@photostructure/fs-metadata 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/CHANGELOG.md +11 -6
  2. package/CLAUDE.md +160 -136
  3. package/CODE_OF_CONDUCT.md +11 -11
  4. package/CONTRIBUTING.md +2 -2
  5. package/README.md +34 -84
  6. package/binding.gyp +98 -23
  7. package/claude.sh +23 -0
  8. package/dist/index.cjs +53 -22
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +5 -0
  11. package/dist/index.d.mts +5 -0
  12. package/dist/index.d.ts +5 -0
  13. package/dist/index.mjs +52 -21
  14. package/dist/index.mjs.map +1 -1
  15. package/{C++_REVIEW_TODO.md → doc/C++_REVIEW_TODO.md} +97 -25
  16. package/doc/GPG_RELEASE_HOWTO.md +505 -0
  17. package/doc/MACOS_API_REFERENCE.md +469 -0
  18. package/doc/SECURITY_AUDIT_2025.md +809 -0
  19. package/doc/SSH_RELEASE_HOWTO.md +207 -0
  20. package/doc/WINDOWS_API_REFERENCE.md +422 -0
  21. package/doc/WINDOWS_ARM64_SECURITY.md +161 -0
  22. package/doc/WINDOWS_DEBUG_GUIDE.md +96 -0
  23. package/doc/examples.md +267 -0
  24. package/doc/gotchas.md +297 -0
  25. package/doc/logo.png +0 -0
  26. package/doc/logo.svg +85 -0
  27. package/doc/macos-asan-sip-issue.md +71 -0
  28. package/doc/social.png +0 -0
  29. package/doc/social.svg +125 -0
  30. package/doc/windows-build.md +226 -0
  31. package/doc/windows-clang-tidy.md +72 -0
  32. package/doc/windows-memory-testing.md +108 -0
  33. package/doc/windows-prebuildify-arm64.md +232 -0
  34. package/jest.config.cjs +24 -0
  35. package/package.json +68 -44
  36. package/prebuilds/darwin-arm64/@photostructure+fs-metadata.glibc.node +0 -0
  37. package/prebuilds/darwin-x64/@photostructure+fs-metadata.glibc.node +0 -0
  38. package/prebuilds/linux-arm64/@photostructure+fs-metadata.glibc.node +0 -0
  39. package/prebuilds/linux-arm64/@photostructure+fs-metadata.musl.node +0 -0
  40. package/prebuilds/linux-x64/@photostructure+fs-metadata.glibc.node +0 -0
  41. package/prebuilds/linux-x64/@photostructure+fs-metadata.musl.node +0 -0
  42. package/prebuilds/win32-arm64/@photostructure+fs-metadata.glibc.node +0 -0
  43. package/prebuilds/win32-x64/@photostructure+fs-metadata.glibc.node +0 -0
  44. package/scripts/check-memory.ts +186 -0
  45. package/scripts/clang-tidy.ts +832 -0
  46. package/scripts/install.cjs +42 -0
  47. package/scripts/is-platform.mjs +1 -1
  48. package/scripts/macos-asan.sh +155 -0
  49. package/scripts/post-build.mjs +3 -3
  50. package/scripts/prebuild-linux-glibc.sh +119 -0
  51. package/scripts/prebuildify-wrapper.ts +77 -0
  52. package/scripts/precommit.ts +70 -0
  53. package/scripts/sanitizers-test.sh +7 -1
  54. package/scripts/{configure.mjs → setup-native.mjs} +4 -1
  55. package/src/binding.cpp +1 -1
  56. package/src/common/error_utils.h +0 -6
  57. package/src/common/volume_metadata.h +6 -0
  58. package/src/darwin/hidden.cpp +73 -25
  59. package/src/darwin/path_security.h +149 -0
  60. package/src/darwin/raii_utils.h +104 -4
  61. package/src/darwin/volume_metadata.cpp +132 -58
  62. package/src/darwin/volume_mount_points.cpp +80 -47
  63. package/src/hidden.ts +36 -13
  64. package/src/linux/gio_mount_points.cpp +17 -18
  65. package/src/linux/gio_utils.cpp +92 -37
  66. package/src/linux/gio_utils.h +11 -5
  67. package/src/linux/gio_volume_metadata.cpp +111 -48
  68. package/src/linux/volume_metadata.cpp +67 -4
  69. package/src/object.ts +1 -0
  70. package/src/options.ts +6 -0
  71. package/src/path.ts +11 -0
  72. package/src/platform.ts +25 -0
  73. package/src/remote_info.ts +5 -3
  74. package/src/stack_path.ts +8 -6
  75. package/src/string_enum.ts +1 -0
  76. package/src/test-utils/benchmark-harness.ts +192 -0
  77. package/src/test-utils/debuglog-child.ts +30 -2
  78. package/src/test-utils/debuglog-enabled-child.ts +38 -8
  79. package/src/test-utils/jest-setup.ts +14 -0
  80. package/src/test-utils/memory-test-core.ts +336 -0
  81. package/src/test-utils/memory-test-runner.ts +108 -0
  82. package/src/test-utils/platform.ts +46 -1
  83. package/src/test-utils/worker-thread-helper.cjs +157 -26
  84. package/src/types/native_bindings.ts +1 -1
  85. package/src/types/options.ts +6 -0
  86. package/src/windows/drive_status.h +133 -163
  87. package/src/windows/error_utils.h +54 -3
  88. package/src/windows/fs_meta.h +1 -1
  89. package/src/windows/hidden.cpp +60 -43
  90. package/src/windows/security_utils.h +250 -0
  91. package/src/windows/string.h +68 -11
  92. package/src/windows/system_volume.h +1 -1
  93. package/src/windows/thread_pool.h +206 -0
  94. package/src/windows/volume_metadata.cpp +11 -6
  95. package/src/windows/volume_mount_points.cpp +8 -7
  96. package/src/windows/windows_arch.h +39 -0
  97. package/scripts/check-memory.mjs +0 -123
  98. package/scripts/clang-tidy.mjs +0 -73
@@ -0,0 +1,207 @@
1
+ # SSH Bot Setup for GitHub Actions
2
+
3
+ This document explains how to set up SSH commit signing for the GitHub Actions release workflow. SSH signing is newer and simpler than GPG signing, with full GitHub support.
4
+
5
+ ## 0. Create Bot Account (Recommended)
6
+
7
+ For professional projects, create a dedicated bot account rather than using your personal account:
8
+
9
+ ### Create the Bot Account
10
+
11
+ 1. Sign out of your personal GitHub account
12
+ 2. Go to https://github.com/join
13
+ 3. Create account with username like `photostructure-bot`
14
+ 4. Use email: `photostructure-bot@users.noreply.github.com`
15
+ 5. Verify the email address
16
+
17
+ ### Add Bot as Repository Collaborator
18
+
19
+ 1. Go to your repo: `https://github.com/photostructure/fs-metadata`
20
+ 2. Click **Settings** tab
21
+ 3. Click **Collaborators** in the left sidebar
22
+ 4. Click **Add people**
23
+ 5. Search for your bot account username
24
+ 6. Select **Write** permission level (needed for pushes and releases)
25
+ 7. Send invitation
26
+
27
+ ### Bot Accepts Invitation
28
+
29
+ 1. Sign in as the bot account
30
+ 2. Check notifications or email for repository invitation
31
+ 3. Accept the invitation
32
+
33
+ ## 1. Generate SSH Signing Key
34
+
35
+ Generate an Ed25519 SSH key specifically for commit signing:
36
+
37
+ ```bash
38
+ # Generate the key pair
39
+ ssh-keygen -t ed25519 -f ~/.ssh/photostructure-bot-signing -N "" -C "photostructure-bot"
40
+
41
+ # Display the public key (you'll need this for GitHub)
42
+ cat ~/.ssh/photostructure-bot-signing.pub
43
+ ```
44
+
45
+ ## 2. Add SSH Key to GitHub Bot Account
46
+
47
+ **Important**: Add the key to the **bot account**, not your personal account.
48
+
49
+ 1. Sign in as `photostructure-bot`
50
+ 2. Go to Settings → SSH and GPG keys
51
+ 3. Click **New SSH key**
52
+ 4. **Critical**: For "Key type", select **"Signing Key"** (not "Authentication Key")
53
+ 5. Title: `fs-metadata Release Signing Key`
54
+ 6. Key: Paste the contents of `~/.ssh/photostructure-bot-signing.pub`
55
+ 7. Click **Add SSH key**
56
+
57
+ ## 3. Configure Repository Secrets
58
+
59
+ Add the private key to your repository secrets:
60
+
61
+ ### Copy the Private Key
62
+
63
+ ```bash
64
+ # Copy private key to clipboard (macOS)
65
+ cat ~/.ssh/photostructure-bot-signing | pbcopy
66
+
67
+ # Copy private key to clipboard (Linux with xclip)
68
+ cat ~/.ssh/photostructure-bot-signing | xclip -selection clipboard
69
+
70
+ # Copy private key to clipboard (Windows with clip)
71
+ cat ~/.ssh/photostructure-bot-signing | clip
72
+ ```
73
+
74
+ ### Add Repository Secrets
75
+
76
+ 1. Go to your repository settings
77
+ 2. Navigate to Settings → Secrets and variables → Actions
78
+ 3. Add these secrets:
79
+
80
+ | Secret Name | Value |
81
+ | ----------------- | ----------------------------- |
82
+ | `SSH_SIGNING_KEY` | Paste the private key content |
83
+ | `GIT_USER_NAME` | `photostructure-bot` |
84
+ | `GIT_USER_EMAIL` | `bot@photostructure.com` |
85
+ | `NPM_TOKEN` | Your npm authentication token |
86
+
87
+ ## 4. How SSH Signing Works in Actions
88
+
89
+ The SSH signing setup uses the [photostructure/git-ssh-signing-action](https://github.com/marketplace/actions/git-ssh-signing-action):
90
+
91
+ ### Features
92
+
93
+ - Installs the SSH private key
94
+ - Configures Git to use SSH signing format
95
+ - Sets up commit and tag signing
96
+ - Creates allowed signers file for verification
97
+ - Automatically cleans up keys and configuration after workflow
98
+ - Supports Linux and macOS runners
99
+ - Requires Git 2.34.0+
100
+
101
+ ## 5. Using SSH Signing in Workflows
102
+
103
+ ### Basic Workflow Example
104
+
105
+ ```yaml
106
+ jobs:
107
+ publish:
108
+ runs-on: ubuntu-latest
109
+ permissions:
110
+ contents: write
111
+ steps:
112
+ - uses: actions/checkout@v4
113
+ with:
114
+ fetch-depth: 0
115
+
116
+ - uses: photostructure/git-ssh-signing-action@v1
117
+ with:
118
+ ssh-signing-key: ${{ secrets.SSH_SIGNING_KEY }}
119
+ git-user-name: ${{ secrets.GIT_USER_NAME }}
120
+ git-user-email: ${{ secrets.GIT_USER_EMAIL }}
121
+
122
+ # Your build and release steps here
123
+ - run: npm ci
124
+ - run: npm version patch
125
+ - run: git push origin main --follow-tags
126
+
127
+ # Note: Cleanup is handled automatically by the action
128
+ ```
129
+
130
+ ## 6. Testing SSH Signing
131
+
132
+ Test your setup before using it in production:
133
+
134
+ ```bash
135
+ # Run the SSH signing test workflow
136
+ gh workflow run test-ssh-actions.yml
137
+
138
+ # Check the workflow status
139
+ gh run list --workflow=test-ssh-actions.yml
140
+ ```
141
+
142
+ ## 7. Pre-Release Checklist
143
+
144
+ Before triggering a release:
145
+
146
+ - [ ] **SSH_SIGNING_KEY** secret is configured in repository
147
+ - [ ] **GIT_USER_NAME** and **GIT_USER_EMAIL** secrets are set
148
+ - [ ] **NPM_TOKEN** is valid with publish permissions
149
+ - [ ] SSH public key is added to bot's GitHub account as **Signing Key**
150
+ - [ ] Bot account has **write access** to the repository
151
+ - [ ] Test workflow passes: `gh workflow run test-ssh-actions.yml`
152
+ - [ ] You're on the main branch with latest changes
153
+
154
+ ## 8. Advantages of SSH Signing
155
+
156
+ | Feature | SSH Signing | GPG Signing |
157
+ | ------------------- | ------------ | -------------- |
158
+ | Setup complexity | Simple | Complex |
159
+ | Key generation | One command | Multiple steps |
160
+ | Passphrase handling | Not required | Required |
161
+ | Wrapper scripts | Not needed | Required |
162
+ | GitHub verification | ✓ Supported | ✓ Supported |
163
+ | Maintenance | Minimal | Higher |
164
+
165
+ ## 9. Security Best Practices
166
+
167
+ 1. **Use Ed25519 keys**: Most secure and efficient algorithm
168
+ 2. **Dedicated signing keys**: Don't reuse authentication keys for signing
169
+ 3. **Bot accounts**: Use dedicated accounts for automation
170
+ 4. **Rotate keys**: Consider rotating every 2-3 years
171
+ 5. **Secure storage**: Never commit private keys to repositories
172
+
173
+ ## 10. Cleanup
174
+
175
+ After setting up, securely remove local key copies:
176
+
177
+ ```bash
178
+ # Remove the local key files
179
+ rm ~/.ssh/photostructure-bot-signing
180
+ rm ~/.ssh/photostructure-bot-signing.pub
181
+
182
+ # Or move to secure backup location
183
+ mv ~/.ssh/photostructure-bot-signing* ~/secure-backup/
184
+ ```
185
+
186
+ ## 11. Troubleshooting
187
+
188
+ ### Commits show as "Unverified"
189
+
190
+ - Ensure the SSH key is added as a **Signing Key** (not Authentication Key)
191
+ - Verify the email in Git config matches the GitHub account email
192
+ - Check that the bot account owns the key
193
+
194
+ ### "error: Load key failed"
195
+
196
+ - Verify SSH_SIGNING_KEY secret contains the complete private key
197
+ - Check for extra newlines or spaces in the secret
198
+
199
+ ### Permission denied on push
200
+
201
+ - Ensure bot account has write access to the repository
202
+
203
+ ## References
204
+
205
+ - [GitHub SSH Commit Verification](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#ssh-commit-signature-verification)
206
+ - [Git SSH Signing Documentation](https://git-scm.com/docs/git-config#Documentation/git-config.txt-gpgformat)
207
+ - [GitHub Actions Encrypted Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets)
@@ -0,0 +1,422 @@
1
+ # Windows API Reference Guide
2
+
3
+ ## Overview
4
+
5
+ This document serves as a comprehensive reference for all Windows APIs used in the fs-metadata project, with links to official Microsoft documentation and best practices.
6
+
7
+ ## File System APIs
8
+
9
+ ### GetLogicalDriveStringsW
10
+
11
+ - **Docs**: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getlogicaldrivestringsw
12
+ - **Purpose**: Fills a buffer with strings that specify valid drives in the system
13
+ - **Security**: Buffer overflow risk if size not checked
14
+ - **Best Practice**: Always call with NULL buffer first to get required size
15
+
16
+ ```cpp
17
+ DWORD size = GetLogicalDriveStringsW(0, nullptr);
18
+ if (size > 0) {
19
+ std::unique_ptr<WCHAR[]> buffer(new WCHAR[size]);
20
+ if (GetLogicalDriveStringsW(size, buffer.get())) {
21
+ // Process drives
22
+ }
23
+ }
24
+ ```
25
+
26
+ ### GetDriveTypeW / GetDriveTypeA
27
+
28
+ - **Docs**: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdrivetypew
29
+ - **Purpose**: Determines whether a disk drive is removable, fixed, CD-ROM, RAM disk, or network drive
30
+ - **Return Values**:
31
+ - `DRIVE_UNKNOWN` (0)
32
+ - `DRIVE_NO_ROOT_DIR` (1)
33
+ - `DRIVE_REMOVABLE` (2)
34
+ - `DRIVE_FIXED` (3)
35
+ - `DRIVE_REMOTE` (4)
36
+ - `DRIVE_CDROM` (5)
37
+ - `DRIVE_RAMDISK` (6)
38
+
39
+ ### GetVolumeInformationW / GetVolumeInformationA
40
+
41
+ - **Docs**: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationw
42
+ - **Purpose**: Retrieves information about the file system and volume
43
+ - **Parameters**:
44
+ ```cpp
45
+ BOOL GetVolumeInformationW(
46
+ LPCWSTR lpRootPathName, // Root directory
47
+ LPWSTR lpVolumeNameBuffer, // Volume name buffer
48
+ DWORD nVolumeNameSize, // Length of name buffer
49
+ LPDWORD lpVolumeSerialNumber, // Volume serial number
50
+ LPDWORD lpMaximumComponentLength, // Max file name length
51
+ LPDWORD lpFileSystemFlags, // File system options
52
+ LPWSTR lpFileSystemNameBuffer, // File system name buffer
53
+ DWORD nFileSystemNameSize // Length of file system name buffer
54
+ );
55
+ ```
56
+ - **Common Errors**:
57
+ - `ERROR_NOT_READY`: Drive not ready (CD/DVD)
58
+ - `ERROR_PATH_NOT_FOUND`: Invalid path
59
+
60
+ ### GetDiskFreeSpaceExA
61
+
62
+ - **Docs**: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdiskfreespaceexa
63
+ - **Purpose**: Retrieves disk space information
64
+ - **Thread Safety**: Can block on network drives
65
+ - **Parameters**:
66
+ ```cpp
67
+ BOOL GetDiskFreeSpaceExA(
68
+ LPCSTR lpDirectoryName, // Directory name
69
+ PULARGE_INTEGER lpFreeBytesAvailableToCaller, // Bytes available
70
+ PULARGE_INTEGER lpTotalNumberOfBytes, // Total bytes
71
+ PULARGE_INTEGER lpTotalNumberOfFreeBytes // Free bytes
72
+ );
73
+ ```
74
+
75
+ ### GetVolumeNameForVolumeMountPointW
76
+
77
+ - **Docs**: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumenameforvolumemountpointw
78
+ - **Purpose**: Retrieves a volume GUID path for a volume mount point
79
+ - **Requirements**: Input path must end with backslash
80
+ - **Buffer Size**: 50 characters is sufficient for GUID path
81
+
82
+ ```cpp
83
+ WCHAR volumeGUID[50];
84
+ if (GetVolumeNameForVolumeMountPointW(L"C:\\", volumeGUID, 50)) {
85
+ // volumeGUID contains \\?\Volume{GUID}\
86
+ }
87
+ ```
88
+
89
+ ### FindFirstFileExA
90
+
91
+ - **Docs**: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfileexa
92
+ - **Purpose**: Searches a directory for files/subdirectories
93
+ - **Flags**:
94
+ - `FIND_FIRST_EX_LARGE_FETCH`: Optimize for large directories
95
+ - `FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY`: Skip reparse points
96
+ - **Security**: Can follow symbolic links if not careful
97
+
98
+ ### GetFileAttributesW / SetFileAttributesW
99
+
100
+ - **Docs**:
101
+ - https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileattributesw
102
+ - https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesw
103
+ - **Purpose**: Get/set file attributes including hidden flag
104
+ - **Common Attributes**:
105
+ - `FILE_ATTRIBUTE_HIDDEN` (0x2)
106
+ - `FILE_ATTRIBUTE_SYSTEM` (0x4)
107
+ - `FILE_ATTRIBUTE_DIRECTORY` (0x10)
108
+ - `FILE_ATTRIBUTE_NORMAL` (0x80)
109
+ - **Error Handling**: Returns `INVALID_FILE_ATTRIBUTES` on error
110
+
111
+ ## Network APIs
112
+
113
+ ### WNetGetConnectionA
114
+
115
+ - **Docs**: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/nf-winnetwk-wnetgetconnectiona
116
+ - **Purpose**: Retrieves network connection for a local device
117
+ - **Header**: `#include <winnetwk.h>`
118
+ - **Library**: `-lMpr.lib`
119
+ - **Buffer Management**:
120
+ ```cpp
121
+ DWORD bufferSize = MAX_PATH;
122
+ std::unique_ptr<char[]> buffer(new char[bufferSize]);
123
+ DWORD result = WNetGetConnectionA("Z:", buffer.get(), &bufferSize);
124
+ if (result == ERROR_MORE_DATA) {
125
+ buffer.reset(new char[bufferSize]);
126
+ result = WNetGetConnectionA("Z:", buffer.get(), &bufferSize);
127
+ }
128
+ ```
129
+
130
+ ## String Conversion APIs
131
+
132
+ ### MultiByteToWideChar
133
+
134
+ - **Docs**: https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar
135
+ - **Purpose**: Maps a character string to a UTF-16 wide character string
136
+ - **Security**: Use `MB_ERR_INVALID_CHARS` to detect invalid sequences
137
+ - **Integer Overflow Protection**: Always validate returned size before allocation
138
+ - **Pattern**:
139
+
140
+ ```cpp
141
+ int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
142
+ source, -1, nullptr, 0);
143
+ if (len <= 0) {
144
+ // Handle error
145
+ return L"";
146
+ }
147
+
148
+ // Validate size to prevent overflow
149
+ if (len > PATHCCH_MAX_CCH) {
150
+ throw std::runtime_error("String too long for conversion");
151
+ }
152
+
153
+ std::wstring result(static_cast<size_t>(len - 1), L'\0');
154
+ int written = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
155
+ source, -1, &result[0], len);
156
+ if (written <= 0) {
157
+ throw std::runtime_error("Conversion failed");
158
+ }
159
+ ```
160
+
161
+ ### WideCharToMultiByte
162
+
163
+ - **Docs**: https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte
164
+ - **Purpose**: Maps a UTF-16 wide character string to a character string
165
+ - **Flags**: Use `WC_ERR_INVALID_CHARS` for Vista+ to detect errors
166
+ - **Integer Overflow Protection**: Validate size before `size - 1` subtraction
167
+ - **Pattern**:
168
+
169
+ ```cpp
170
+ int size = WideCharToMultiByte(CP_UTF8, 0, wide, -1, nullptr, 0, nullptr, nullptr);
171
+
172
+ if (size <= 0) {
173
+ return ""; // Conversion failed
174
+ }
175
+
176
+ // Check for overflow and excessive size (1MB limit recommended)
177
+ if (size > INT_MAX - 1 || size > 1024 * 1024) {
178
+ throw std::runtime_error("String conversion size exceeds reasonable limits");
179
+ }
180
+
181
+ std::string result(static_cast<size_t>(size - 1), 0);
182
+ int written = WideCharToMultiByte(CP_UTF8, 0, wide, -1, &result[0], size, nullptr, nullptr);
183
+ if (written <= 0) {
184
+ throw std::runtime_error("String conversion failed");
185
+ }
186
+ return result;
187
+ ```
188
+
189
+ ## Shell APIs
190
+
191
+ ### SHGetFolderPathW
192
+
193
+ - **Docs**: https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetfolderpathw
194
+ - **Purpose**: Gets path to special folders
195
+ - **Common CSIDLs**:
196
+ - `CSIDL_WINDOWS`: Windows directory
197
+ - `CSIDL_SYSTEM`: System directory
198
+ - `CSIDL_PROGRAM_FILES`: Program Files
199
+ - **Buffer Size**: MAX_PATH is always sufficient
200
+
201
+ ### PathCchCanonicalizeEx
202
+
203
+ - **Docs**: https://docs.microsoft.com/en-us/windows/win32/api/pathcch/nf-pathcch-pathcchcanonicalize
204
+ - **Purpose**: Simplifies a path by removing navigation elements with extended options
205
+ - **Header**: `#include <pathcch.h>`
206
+ - **Library**: `-lPathcch.lib`
207
+ - **Security**: Prevents directory traversal attacks
208
+ - **Long Path Support**: Use `PATHCCH_ALLOW_LONG_PATHS` flag for paths > MAX_PATH (260 chars)
209
+ - **Buffer Size**: `PATHCCH_MAX_CCH` (32,768 characters) supports Windows 10+ long paths
210
+ - **Usage**:
211
+ ```cpp
212
+ wchar_t canonicalPath[PATHCCH_MAX_CCH];
213
+ HRESULT hr = PathCchCanonicalizeEx(
214
+ canonicalPath,
215
+ PATHCCH_MAX_CCH,
216
+ inputPath,
217
+ PATHCCH_ALLOW_LONG_PATHS // Enable long path support
218
+ );
219
+ ```
220
+ - **Note**: Supersedes `PathCchCanonicalize` which is limited to MAX_PATH (260 chars)
221
+
222
+ ## Threading APIs
223
+
224
+ ### CreateThread
225
+
226
+ - **Docs**: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread
227
+ - **Best Practice**: Always store handle for cleanup
228
+ - **Never**: Never use `TerminateThread` - it can corrupt process state
229
+
230
+ ### WaitForSingleObject / WaitForMultipleObjects
231
+
232
+ - **Docs**: https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject
233
+ - **Timeout**: Use INFINITE carefully - prefer specific timeouts
234
+ - **Return Values**:
235
+ - `WAIT_OBJECT_0`: Success
236
+ - `WAIT_TIMEOUT`: Timeout elapsed
237
+ - `WAIT_FAILED`: Error occurred
238
+
239
+ ### Critical Sections
240
+
241
+ - **Initialize**: `InitializeCriticalSection`
242
+ - **Enter**: `EnterCriticalSection` (blocking)
243
+ - **Try Enter**: `TryEnterCriticalSection` (non-blocking)
244
+ - **Leave**: `LeaveCriticalSection`
245
+ - **Delete**: `DeleteCriticalSection`
246
+ - **Best Practice**: Use RAII wrapper to ensure cleanup
247
+
248
+ ## Memory Management
249
+
250
+ ### Heap Functions
251
+
252
+ - **HeapAlloc**: Allocate memory from heap
253
+ - **HeapFree**: Free heap memory
254
+ - **HeapValidate**: Validate heap integrity
255
+ - **GetProcessHeap**: Get default process heap
256
+
257
+ ### Debug CRT Functions (Debug builds only)
258
+
259
+ ```cpp
260
+ #ifdef _DEBUG
261
+ #define _CRTDBG_MAP_ALLOC
262
+ #include <crtdbg.h>
263
+
264
+ // Enable leak detection
265
+ _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
266
+
267
+ // Set allocation breakpoint
268
+ _CrtSetBreakAlloc(1234); // Break on allocation #1234
269
+
270
+ // Check memory state
271
+ _CrtCheckMemory();
272
+
273
+ // Dump leaks on exit
274
+ _CrtDumpMemoryLeaks();
275
+ #endif
276
+ ```
277
+
278
+ ## Security Considerations
279
+
280
+ ### Path Validation
281
+
282
+ 1. Check for null bytes
283
+ 2. Check for directory traversal (..)
284
+ 3. Check for device names (CON, PRN, AUX, etc.)
285
+ 4. Check for alternate data streams
286
+ 5. Validate UTF-8 sequences
287
+ 6. Use `PathCchCanonicalizeEx` with `PATHCCH_ALLOW_LONG_PATHS` for normalization
288
+ 7. Validate path length:
289
+ - Legacy limit: MAX_PATH (260 characters)
290
+ - Windows 10+ limit: PATHCCH_MAX_CCH (32,768 wide characters)
291
+ - UTF-8 validation: Account for multi-byte sequences (up to 3 bytes per wide char)
292
+
293
+ ### Buffer Overflow Prevention
294
+
295
+ 1. Always check buffer sizes
296
+ 2. Use safe string functions (StringCch\*)
297
+ 3. Validate all input lengths
298
+ 4. Use dynamic allocation when size unknown
299
+ 5. **Integer overflow checks** for string conversions:
300
+ - Validate `MultiByteToWideChar`/`WideCharToMultiByte` return values
301
+ - Check `size > INT_MAX - 1` before subtraction
302
+ - Enforce reasonable size limits (e.g., 1MB for general strings)
303
+ - Use `static_cast<size_t>()` for allocations to prevent sign issues
304
+
305
+ ### Handle Management
306
+
307
+ 1. Always close handles
308
+ 2. Use RAII wrappers
309
+ 3. Check for INVALID_HANDLE_VALUE
310
+ 4. Never use handles after closing
311
+
312
+ ### Memory Management with Windows APIs
313
+
314
+ 1. **FormatMessage with FORMAT_MESSAGE_ALLOCATE_BUFFER**:
315
+ - Always use RAII wrapper (LocalFreeGuard) to prevent leaks
316
+ - If `std::string` constructor throws, buffer must still be freed
317
+ - Never rely on manual `LocalFree` after potential throwing operations
318
+ 2. **HeapAlloc/LocalAlloc**:
319
+ - Prefer RAII wrappers over manual free calls
320
+ - Use smart pointers or custom deleters for C++ integration
321
+ 3. **Exception Safety**:
322
+ - Any API that allocates memory must have cleanup in destructor
323
+ - Never assume operations won't throw in low-memory conditions
324
+
325
+ ### Thread Safety
326
+
327
+ 1. Protect shared data with synchronization
328
+ 2. Use atomic operations where appropriate
329
+ 3. Avoid blocking operations in critical sections
330
+ 4. Always clean up threads gracefully
331
+
332
+ ## Error Handling
333
+
334
+ ### Common Error Codes
335
+
336
+ - `ERROR_SUCCESS` (0): Operation successful
337
+ - `ERROR_FILE_NOT_FOUND` (2): File not found
338
+ - `ERROR_PATH_NOT_FOUND` (3): Path not found
339
+ - `ERROR_ACCESS_DENIED` (5): Access denied
340
+ - `ERROR_INVALID_HANDLE` (6): Invalid handle
341
+ - `ERROR_NOT_READY` (21): Device not ready
342
+ - `ERROR_SHARING_VIOLATION` (32): File in use
343
+ - `ERROR_NETWORK_ACCESS_DENIED` (65): Network access denied
344
+ - `ERROR_BAD_NETPATH` (53): Network path not found
345
+ - `ERROR_MORE_DATA` (234): More data available
346
+ - `ERROR_NO_MORE_FILES` (18): No more files
347
+
348
+ ### FormatMessage
349
+
350
+ - **Docs**: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessagea
351
+ - **Memory Management**: With `FORMAT_MESSAGE_ALLOCATE_BUFFER`, must call `LocalFree`
352
+ - **Exception Safety**: Use RAII wrapper to prevent leaks if exceptions thrown
353
+ - **Pattern**:
354
+
355
+ ```cpp
356
+ // UNSAFE - memory leak if exception thrown:
357
+ LPVOID msgBuffer;
358
+ FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | ..., &msgBuffer, ...);
359
+ std::string msg((LPSTR)msgBuffer); // If this throws, LocalFree never called!
360
+ LocalFree(msgBuffer);
361
+
362
+ // SAFE - RAII wrapper ensures cleanup:
363
+ struct LocalFreeGuard {
364
+ LPVOID ptr;
365
+ explicit LocalFreeGuard(LPVOID p) : ptr(p) {}
366
+ ~LocalFreeGuard() { if (ptr) LocalFree(ptr); }
367
+ LocalFreeGuard(const LocalFreeGuard&) = delete;
368
+ LocalFreeGuard& operator=(const LocalFreeGuard&) = delete;
369
+ };
370
+
371
+ LPVOID msgBuffer = nullptr;
372
+ FormatMessageA(
373
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
374
+ FORMAT_MESSAGE_FROM_SYSTEM |
375
+ FORMAT_MESSAGE_IGNORE_INSERTS,
376
+ NULL,
377
+ GetLastError(),
378
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
379
+ (LPSTR)&msgBuffer,
380
+ 0,
381
+ NULL
382
+ );
383
+
384
+ LocalFreeGuard guard(msgBuffer); // RAII ensures LocalFree called
385
+ if (msgBuffer) {
386
+ std::string msg((LPSTR)msgBuffer); // Now safe - guard cleans up
387
+ // ... use msg
388
+ }
389
+ // guard destructor calls LocalFree automatically
390
+ ```
391
+
392
+ ## Testing Recommendations
393
+
394
+ ### Memory Leak Detection
395
+
396
+ ```bash
397
+ # Build debug version
398
+ node-gyp rebuild --debug
399
+
400
+ # Set CRT debug flags
401
+ set _CRTDBG_MAP_ALLOC=1
402
+
403
+ # Run with leak detection
404
+ node --expose-gc test.js
405
+ ```
406
+
407
+ ### Address Sanitizer
408
+
409
+ ```bash
410
+ # Set ASan options
411
+ set ASAN_OPTIONS=halt_on_error=0:print_stats=1:check_initialization_order=1
412
+
413
+ # Run tests
414
+ npm test
415
+ ```
416
+
417
+ ### Performance Profiling
418
+
419
+ 1. Use Visual Studio Performance Profiler
420
+ 2. Enable heap profiling
421
+ 3. Monitor handle counts in Task Manager
422
+ 4. Use Performance Monitor for system metrics