larksuite/cli · error

%s: path must be absolute, got %q

Error message

%s: path must be absolute, got %q

What it means

requireAbsolutePath (invoked via AssertSecurePath in the binding security audit) rejects any path that is not absolute under Go's filepath.IsAbs rules. The audit is intentionally stdlib-strict: shortcuts like '~' are home-relative and must be pre-resolved by callers such as resolveFileRef before audit. The label names which audited path failed.

Source

Thrown at internal/binding/audit.go:75

	if err := auditFilePermissions(effectivePath, params.AllowReadableByOthers, label); err != nil {
		return "", err
	}
	if err := checkOwnerUID(effectivePath, label); err != nil {
		return "", err
	}
	return effectivePath, nil
}

// requireAbsolutePath rejects relative paths; relative paths would depend on
// the process cwd and defeat the point of a static audit. Shell-style
// shortcuts like `~` are home-relative, not cwd-relative — they are an
// orthogonal concern and the audit is intentionally Go-stdlib strict here.
// Callers that accept user-authored config (e.g. resolveFileRef) must
// pre-resolve any such shortcuts before passing the path in.
func requireAbsolutePath(target, label string) error {
	if !filepath.IsAbs(target) {
		return fmt.Errorf("%s: path must be absolute, got %q", label, target)
	}
	return nil
}

// lstatNonDir stats the path without following symlinks, rejecting
// directories. Returns the stat info for downstream steps to reuse.
func lstatNonDir(target, label string) (fs.FileInfo, error) {
	info, err := vfs.Lstat(target)
	if err != nil {
		return nil, fmt.Errorf("%s: cannot stat %q: %w", label, target, err)
	}
	if info.IsDir() {
		return nil, fmt.Errorf("%s: path %q is a directory, not a file", label, target)
	}
	return info, nil
}

// resolveSymlinkIfAllowed resolves a symlink to its target when

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Convert to an absolute path at the call site: filepath.Abs(p) or home-dir expansion for '~' before calling AssertSecurePath.
  2. For '~', expand to os.UserHomeDir()-based absolute path in the config-loading layer (resolveFileRef), not in the audit.
  3. Fix the config value itself to be an absolute path if it's user-authored.
  4. Check the label in the message to identify which of the audited paths (file, target, symlink) is relative.

Example fix

// before
err := binding.AssertSecurePath(cfg.Path, params)
// after
abs, _ := filepath.Abs(expandTilde(cfg.Path))
err := binding.AssertSecurePath(abs, params)
Defensive patterns

Strategy: validation

Validate before calling

func ensureAbs(p string) (string, error) {
    if strings.HasPrefix(p, "~") {
        home, err := os.UserHomeDir()
        if err != nil { return "", err }
        p = filepath.Join(home, strings.TrimPrefix(p, "~"))
    }
    return filepath.Abs(p)
}
abs, err := ensureAbs(cfg.Path)
if err != nil { return err }
err = binding.AssertSecurePath(abs, params)

Type guard

func isRelativePathErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "path must be absolute")
}

Try / catch

if err := binding.AssertSecurePath(p, params); err != nil {
    if isRelativePathErr(err) {
        abs, aerr := filepath.Abs(p)
        if aerr == nil { return binding.AssertSecurePath(abs, params) }
    }
    return err
}

Prevention

When it happens

Trigger: AssertSecurePath is given a relative path (e.g. 'config.yaml', './x', '../x') or a '~'-prefixed path that the caller failed to expand before auditing.

Common situations: User config file contains a relative path that was never resolved against cwd/home; a '~' shortcut passed straight from user-authored config into the audit; programmatic callers building paths by concatenation without filepath.Abs.

Related errors


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/d3634b56cc4dc9ae. Report an issue: GitHub.