(p)
| 2938 | // (defeating symlink traversal) and accepts only paths in blessedPaths or |
| 2939 | // their draft/backup siblings. |
| 2940 | async function assertWritablePath(p) |
| 2941 | { |
| 2942 | if (typeof p !== 'string' || !p || p.includes('\0')) |
| 2943 | { |
| 2944 | throw new Error('path not authorised'); |
| 2945 | } |
| 2946 | |
| 2947 | const resolved = path.resolve(p); |
| 2948 | let realpath; |
| 2949 | |
| 2950 | try |
| 2951 | { |
| 2952 | realpath = await fsProm.realpath(resolved); |
| 2953 | } |
| 2954 | catch (e) |
| 2955 | { |
| 2956 | // File doesn't exist yet (e.g. Save As to a new file). Canonicalise |
| 2957 | // the parent directory so symlinks in the directory chain are still |
| 2958 | // resolved. |
| 2959 | try |
| 2960 | { |
| 2961 | const parentReal = await fsProm.realpath(path.dirname(resolved)); |
| 2962 | realpath = path.join(parentReal, path.basename(resolved)); |
| 2963 | } |
| 2964 | catch (e2) |
| 2965 | { |
| 2966 | // Neither the file nor its parent could be realpath-canonicalised. |
| 2967 | // This happens on filesystems whose driver doesn't support the |
| 2968 | // underlying call (e.g. WinFSP "local" / Cryptomator, some FUSE |
| 2969 | // mounts), not just on missing paths. realpath is a defence-in-depth |
| 2970 | // measure against symlink traversal; when it's simply unavailable we |
| 2971 | // must not deny an otherwise-blessed write, so fall back to the |
| 2972 | // lexically-resolved path. blessedPaths is still consulted below, so |
| 2973 | // only paths the user authorised through trusted UI are accepted. |
| 2974 | realpath = resolved; |
| 2975 | } |
| 2976 | } |
| 2977 | |
| 2978 | if (realpath.startsWith(appBaseDir)) |
| 2979 | { |
| 2980 | throw new Error('path not authorised'); |
| 2981 | } |
| 2982 | |
| 2983 | // Block writes anywhere inside userData (settings store, plugins, Local |
| 2984 | // Storage). installPlugin has its own write-into-userData flow and is |
| 2985 | // out of scope here; it does not go through assertWritablePath. |
| 2986 | let userDataDir; |
| 2987 | |
| 2988 | try |
| 2989 | { |
| 2990 | userDataDir = path.resolve(app.getPath('userData')); |
| 2991 | } |
| 2992 | catch (e) |
| 2993 | { |
| 2994 | userDataDir = null; |
| 2995 | } |
| 2996 | |
| 2997 | if (userDataDir && (realpath === userDataDir || |
no test coverage detected