{"record":{"id":"54a57e9516c29703","repo":"larksuite/cli","slug":"s-q-is-outside-the-built-in-allowlist-allowed-r","errorCode":null,"errorMessage":"%s %q is outside the built-in allowlist; allowed roots are %s (hint: save under one of the allowed roots; flags that support stdin can read an out-of-tree file via '-')","messagePattern":"(.+?) %q is outside the built-in allowlist; allowed roots are (.+?) \\(hint: save under one of the allowed roots; flags that support stdin can read an out-of-tree file via '-'\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/vfs/localfileio/policy.go","lineNumber":335,"sourceCode":"\n// checkAllow accepts paths under any built-in allow root; everything else is\n// rejected with the full allowlist spelled out. Only the resolved form of the\n// input participates: matching the pre-resolution literal would grant access\n// to any symlink placed inside an allow root, no matter where it points.\nfunc checkAllow(flagName, raw, resolved, cwd string) error {\n\troots := allowRoots(cwd)\n\tfor _, e := range roots {\n\t\tif matchResolved(resolved, e) {\n\t\t\treturn nil\n\t\t}\n\t}\n\t// Identity matching also settles the permissive direction: when an\n\t// ancestor is the very same directory as an allow root, the target really\n\t// is inside it, whatever spelling reached it.\n\tif _, ok := matchByFileIdentity(resolved, roots); ok {\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"%s %q is outside the built-in allowlist; allowed roots are %s \"+\n\t\t\"(hint: save under one of the allowed roots; flags that support stdin can read an out-of-tree file via '-')\",\n\t\tflagName, raw, allowRootsLabel())\n}\n\n// matchResolved reports whether the fully resolved input path falls under the\n// entry in either of the entry's namespaces. Comparing the input's resolved\n// form against the entry literal is safe in both directions: it only matches\n// when the real filesystem location truly is under that literal path.\nfunc matchResolved(resolved string, e policyEntry) bool {\n\treturn isUnderDir(foldCase(resolved), foldCase(e.resolved)) ||\n\t\tisUnderDir(foldCase(resolved), foldCase(e.literal))\n}\n\n// foldCase normalizes case on platforms whose default filesystems compare\n// case-insensitively: NTFS on Windows and APFS/HFS+ on macOS, where\n// ~/.SSH/id_rsa and ~/.ssh/id_rsa are the same file and a byte-for-byte\n// comparison would walk straight past a deny root. Folding can over-match on\n// the rarer case-sensitive volumes of those platforms, which errs toward","sourceCodeStart":317,"sourceCodeEnd":353,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/vfs/localfileio/policy.go#L317-L353","documentation":"The VFS policy is allowlist-based: local paths must resolve inside one of the built-in allowed roots. checkAllow resolves the input, matches it against the roots (including by file identity for symlink/alias cases), and reports the allowed roots in the error, plus a hint that stdin ('-') can supply out-of-tree data.","triggerScenarios":"Passing a file path outside every allow root (e.g. /etc/... or an arbitrary user directory) to a file-accepting flag while the policy runs in allowlist mode; also occurs when a symlink resolves outside the roots.","commonSituations":"Reading an input file from a scratch directory, downloading to a random folder, or CI runners with temp dirs outside the allowed roots.","solutions":["Save/read the file under one of the allowed roots listed in the error message","For input, use '-' (stdin) if the flag supports it: cat /etc/hosts | lark-cli ... --file -","Copy or move the file into an allowed root before invoking the command","Check for symlinks resolving outside the root and reference the real in-root path"],"exampleFix":"// before\nlark-cli im file upload --file /var/tmp/report.pdf\n// after\ncp /var/tmp/report.pdf ~/Downloads/ && lark-cli im file upload --file ~/Downloads/report.pdf","handlingStrategy":"validation","validationCode":"resolved, _ := filepath.EvalSymlinks(path)\nfor _, root := range allowedRoots { // from the error message or schema\n\tif strings.HasPrefix(resolved, root) { return true }\n}\nreturn false","typeGuard":null,"tryCatchPattern":"if strings.Contains(err.Error(), \"outside the built-in allowlist\") {\n\t// parse allowed roots from the message, move the file under one, or pipe via '-'\n}","preventionTips":["Save outputs and stage inputs under one of the documented allowed roots","Use '-' stdin input for out-of-tree read-only data when the flag supports it","Verify symlinks resolve inside the root before passing them"],"tags":["filesystem","security","allowlist"],"backgroundTag":"path-outside-allowlist","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"}