{"record":{"id":"48fbbf6e4faf41bc","repo":"larksuite/cli","slug":"unsafe-proxy-plugin-config-q-w","errorCode":null,"errorMessage":"unsafe proxy plugin config %q: %w","messagePattern":"unsafe proxy plugin config %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/transport/config.go","lineNumber":103,"sourceCode":"\t\t\t\t\tloadCfg = nil\n\t\t\t\t}\n\t\t\t\tloadErr = nil\n\t\t\t\treturn\n\t\t\t}\n\t\t\tloadErr = fmt.Errorf(\"failed to stat proxy plugin config %q: %w\", p, err)\n\t\t\treturn\n\t\t}\n\t\t// Security hardening: this config dictates where ALL outbound CLI traffic\n\t\t// egresses and which extra CA is trusted, so a file another local user or\n\t\t// process can tamper with (symlink, foreign owner, group/world-writable)\n\t\t// could redirect credential traffic. Audit it the same way the CA file is.\n\t\tsafePath, err := binding.AssertSecurePath(binding.AuditParams{\n\t\t\tTargetPath:            p,\n\t\t\tLabel:                 ConfigFileName,\n\t\t\tAllowReadableByOthers: true, // config is not a secret; only writability/owner/symlink matter\n\t\t})\n\t\tif err != nil {\n\t\t\tloadErr = fmt.Errorf(\"unsafe proxy plugin config %q: %w\", p, err)\n\t\t\treturn\n\t\t}\n\t\tb, err := vfs.ReadFile(safePath)\n\t\tif err != nil {\n\t\t\tloadErr = fmt.Errorf(\"failed to read proxy plugin config %q: %w\", p, err)\n\t\t\treturn\n\t\t}\n\t\tvar fileCfg Config\n\t\tif err := json.Unmarshal(b, &fileCfg); err != nil {\n\t\t\tloadErr = fmt.Errorf(\"invalid proxy plugin config %q: %w\", p, err)\n\t\t\treturn\n\t\t}\n\n\t\t// Merge: file base + env overrides.\n\t\tif cfg == nil {\n\t\t\tcfg = &fileCfg\n\t\t} else {\n\t\t\t*cfg = fileCfg","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/transport/config.go#L85-L121","documentation":"transport.Load rejects the proxy plugin config with this error when binding.AssertSecurePath fails its security audit. Because this config decides where ALL outbound CLI traffic egresses and which extra CA is trusted, Load refuses files that a local attacker could tamper with: symlinked paths, foreign ownership, or group/world-writable files (readability by others is allowed since the file is not secret). This prevents credential traffic from being redirected through a hostile proxy.","triggerScenarios":"Loading the CLI transport when ~/.lark-cli/proxy_config.json exists but is a symlink (or inside a symlinked component), owned by another user (not the current UID), or has group-write/world-write permission bits set; AssertSecurePath returns the violation and Load wraps it with the path.","commonSituations":"Dotfile managers (stow, GNU stow, chezmoi, symlinking ~/.lark-cli into a repo) turn the config into a symlink; a shared machine where an admin or installer created the file as root with umask 000; restoring backups that lost original ownership/permissions; running the CLI as a service account while the file was created by the login user.","solutions":["Replace the symlink with a real regular file: rm the symlink and cp the target content into place","Fix ownership so the file belongs to the current user: chown $(id -u) ~/.lark-cli/proxy_config.json","Remove insecure write bits: chmod go-w ~/.lark-cli/proxy_config.json (and chmod go-w ~/.lark-cli if the dir is writable by others)","Prefer proxy env vars (LARKSUITE_CLI_PROXY_ENABLE/ADDRESS, LARKSUITE_CLI_CA_PATH) instead of the file if a symlinked config is unavoidable"],"exampleFix":"// before\nlrwxrwxrwx proxy_config.json -> dotfiles/proxy_config.json\n\n// after\nrm ~/.lark-cli/proxy_config.json\ncp ~/dotfiles/proxy_config.json ~/.lark-cli/proxy_config.json\nchmod 644 ~/.lark-cli/proxy_config.json","handlingStrategy":"validation","validationCode":"func isSafeProxyConfig(p string) error {\n    info, err := os.Lstat(p) // Lstat: fails on symlink targets\n    if err != nil { return err }\n    if info.Mode()&os.ModeSymlink != 0 || info.Mode()&os.ModeType != 0 {\n        return fmt.Errorf(\"%s must be a regular file, not a symlink\", p)\n    }\n    if info.Mode().Perm()&0o022 != 0 {\n        return fmt.Errorf(\"%s must not be group/world writable\", p)\n    }\n    if st, ok := info.Sys().(*syscall.Stat_t); ok && st.Uid != uint32(os.Getuid()) {\n        return fmt.Errorf(\"%s must be owned by the current user\", p)\n    }\n    return nil\n}","typeGuard":"func configIsRegularAndOwned(fi os.FileInfo, uid int) bool {\n    return fi.Mode().IsRegular() &&\n        fi.Mode().Perm()&0o022 == 0 &&\n        stUid(fi) == uid\n}","tryCatchPattern":"cfg, err := transport.Load()\nif err != nil && strings.Contains(err.Error(), \"unsafe proxy plugin config\") {\n    p := extractQuotedPath(err.Error())\n    // replace symlink with real file and tighten perms, then retry\n    _ = os.Remove(p)\n    _ = os.WriteFile(p, nil, 0o644)\n    os.Chmod(p, 0o644)\n}","preventionTips":["Do not symlink ~/.lark-cli or proxy_config.json (dotfile managers: copy instead of link)","Set a safe umask (022) for processes that create the config","Never let installers or other users create files in your ~/.lark-cli","Audit permissions periodically: find ~/.lark-cli -perm -go+w  # should return nothing","If a symlink is unavoidable, configure the proxy via env vars instead of the file"],"tags":["security","proxy-config","symlink","file-permissions"],"backgroundTag":"unsafe-config-file-permissions","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}