larksuite/cli · error

%s %q is inside %s, which is protected by the built-in denyl

Error message

%s %q is inside %s, which is protected by the built-in denylist

What it means

The built-in denylist protects sensitive locations (e.g. config, credentials, CLI-owned state). checkDeny compares the resolved path's ancestors against deny roots — by device/inode identity where possible — and denyError reports the offending flag name, raw value, and protected label.

Source

Thrown at internal/vfs/localfileio/policy.go:293

	roots := slices.Concat(denyRoots(), configDirDenyRoots(cwd))
	for _, e := range roots {
		if matchResolved(resolved, e) || isUnderDir(foldCase(absLiteral), foldCase(e.literal)) {
			return denyError(flagName, raw, e.label)
		}
	}
	// Name comparison alone cannot decide containment: case-insensitive and
	// case-folding filesystems (APFS folds U+017F to "s", so ".ſſh" opens
	// "~/.ssh"), Unicode normalization, and Windows short names all give the
	// same directory several spellings. Ask the kernel instead — file identity
	// has exactly one answer per directory.
	if label, ok := matchByFileIdentity(resolved, roots); ok {
		return denyError(flagName, raw, label)
	}
	return nil
}

func denyError(flagName, raw, label string) error {
	return fmt.Errorf("%s %q is inside %s, which is protected by the built-in denylist", flagName, raw, label)
}

// matchByFileIdentity walks resolved upwards and reports the first root that
// is the very same directory as one of the ancestors, compared by device and
// inode rather than by name. Missing ancestors are skipped: a target that does
// not exist yet is decided by its nearest existing parent.
func matchByFileIdentity(resolved string, roots []policyEntry) (string, bool) {
	p := resolved
	for {
		if fi, err := vfs.Lstat(p); err == nil {
			for _, e := range roots {
				if e.info != nil && os.SameFile(fi, e.info) {
					return e.label, true
				}
			}
		}
		parent := filepath.Dir(p)
		if parent == p {

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Move the file outside the protected root and pass the new path
  2. Check the label in the message to learn which protected root was matched and pick a different working directory
  3. If the operation is legitimate CLI-state management, use the dedicated CLI command for that state instead of a generic file flag

Example fix

// before
--config /home/me/.lark-cli/config.yaml --upload /home/me/.lark-cli/cache.bin
// after
--config /home/me/.lark-cli/config.yaml --upload /tmp/exports/cache.bin
Defensive patterns

Strategy: validation

Validate before calling

// resolve symlinks and check the target is not under a protected root before invoking
resolved, _ := filepath.EvalSymlinks(path)
if strings.HasPrefix(resolved, protectedRoot) {
	// pick another location
}

Try / catch

if strings.Contains(err.Error(), "protected by the built-in denylist") {
	// move the file out of the protected root and retry with the new path
}

Prevention

When it happens

Trigger: Passing a file/directory inside a denylisted root to a flag that accepts local paths (flagName in the message), e.g. --config pointing under the CLI's own state directory or another protected root; matched even via symlink/alias spellings because matching resolves ancestors.

Common situations: Trying to overwrite CLI credentials/config through a data-upload flag, or a symlink pointing into a protected directory that the denylist catches.

Related errors


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