@photostructure/fs-metadata 0.6.1 → 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 (89) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/CLAUDE.md +141 -315
  3. package/CODE_OF_CONDUCT.md +11 -11
  4. package/CONTRIBUTING.md +1 -1
  5. package/README.md +34 -103
  6. package/binding.gyp +97 -22
  7. package/claude.sh +23 -0
  8. package/dist/index.cjs +51 -21
  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 +51 -21
  14. package/dist/index.mjs.map +1 -1
  15. package/doc/C++_REVIEW_TODO.md +97 -25
  16. package/doc/GPG_RELEASE_HOWTO.md +44 -13
  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 +28 -24
  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 +9 -2
  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 +23 -0
  35. package/package.json +61 -36
  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 +690 -99
  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 +12 -1
  51. package/scripts/prebuildify-wrapper.ts +77 -0
  52. package/scripts/precommit.ts +45 -20
  53. package/scripts/sanitizers-test.sh +1 -1
  54. package/src/common/volume_metadata.h +6 -0
  55. package/src/darwin/hidden.cpp +73 -25
  56. package/src/darwin/path_security.h +149 -0
  57. package/src/darwin/raii_utils.h +104 -4
  58. package/src/darwin/volume_metadata.cpp +132 -58
  59. package/src/darwin/volume_mount_points.cpp +80 -47
  60. package/src/hidden.ts +36 -13
  61. package/src/linux/gio_mount_points.cpp +17 -18
  62. package/src/linux/gio_utils.cpp +92 -37
  63. package/src/linux/gio_utils.h +11 -5
  64. package/src/linux/gio_volume_metadata.cpp +111 -48
  65. package/src/linux/volume_metadata.cpp +67 -4
  66. package/src/object.ts +1 -0
  67. package/src/options.ts +6 -0
  68. package/src/path.ts +11 -0
  69. package/src/remote_info.ts +5 -3
  70. package/src/stack_path.ts +8 -6
  71. package/src/string_enum.ts +1 -0
  72. package/src/test-utils/memory-test-core.ts +336 -0
  73. package/src/test-utils/memory-test-runner.ts +108 -0
  74. package/src/test-utils/platform.ts +46 -1
  75. package/src/test-utils/worker-thread-helper.cjs +154 -27
  76. package/src/types/native_bindings.ts +1 -1
  77. package/src/types/options.ts +6 -0
  78. package/src/windows/drive_status.h +133 -163
  79. package/src/windows/error_utils.h +54 -3
  80. package/src/windows/fs_meta.h +1 -1
  81. package/src/windows/hidden.cpp +60 -43
  82. package/src/windows/security_utils.h +250 -0
  83. package/src/windows/string.h +68 -11
  84. package/src/windows/system_volume.h +1 -1
  85. package/src/windows/thread_pool.h +206 -0
  86. package/src/windows/volume_metadata.cpp +11 -6
  87. package/src/windows/volume_mount_points.cpp +8 -7
  88. package/src/windows/windows_arch.h +39 -0
  89. package/scripts/check-memory.mjs +0 -123
@@ -7,6 +7,7 @@ This document explains how to set up SSH commit signing for the GitHub Actions r
7
7
  For professional projects, create a dedicated bot account rather than using your personal account:
8
8
 
9
9
  ### Create the Bot Account
10
+
10
11
  1. Sign out of your personal GitHub account
11
12
  2. Go to https://github.com/join
12
13
  3. Create account with username like `photostructure-bot`
@@ -14,6 +15,7 @@ For professional projects, create a dedicated bot account rather than using your
14
15
  5. Verify the email address
15
16
 
16
17
  ### Add Bot as Repository Collaborator
18
+
17
19
  1. Go to your repo: `https://github.com/photostructure/fs-metadata`
18
20
  2. Click **Settings** tab
19
21
  3. Click **Collaborators** in the left sidebar
@@ -23,6 +25,7 @@ For professional projects, create a dedicated bot account rather than using your
23
25
  7. Send invitation
24
26
 
25
27
  ### Bot Accepts Invitation
28
+
26
29
  1. Sign in as the bot account
27
30
  2. Check notifications or email for repository invitation
28
31
  3. Accept the invitation
@@ -74,27 +77,26 @@ cat ~/.ssh/photostructure-bot-signing | clip
74
77
  2. Navigate to Settings → Secrets and variables → Actions
75
78
  3. Add these secrets:
76
79
 
77
- | Secret Name | Value |
78
- |-------------|-------|
80
+ | Secret Name | Value |
81
+ | ----------------- | ----------------------------- |
79
82
  | `SSH_SIGNING_KEY` | Paste the private key content |
80
- | `GIT_USER_NAME` | `photostructure-bot` |
81
- | `GIT_USER_EMAIL` | `bot@photostructure.com` |
82
- | `NPM_TOKEN` | Your npm authentication token |
83
+ | `GIT_USER_NAME` | `photostructure-bot` |
84
+ | `GIT_USER_EMAIL` | `bot@photostructure.com` |
85
+ | `NPM_TOKEN` | Your npm authentication token |
83
86
 
84
87
  ## 4. How SSH Signing Works in Actions
85
88
 
86
- The SSH signing setup uses two composite actions:
89
+ The SSH signing setup uses the [photostructure/git-ssh-signing-action](https://github.com/marketplace/actions/git-ssh-signing-action):
90
+
91
+ ### Features
87
92
 
88
- ### setup-ssh-bot
89
93
  - Installs the SSH private key
90
94
  - Configures Git to use SSH signing format
91
95
  - Sets up commit and tag signing
92
96
  - Creates allowed signers file for verification
93
-
94
- ### cleanup-ssh-bot
95
- - Removes SSH keys from the runner
96
- - Clears Git signing configuration
97
- - Ensures no secrets remain after workflow
97
+ - Automatically cleans up keys and configuration after workflow
98
+ - Supports Linux and macOS runners
99
+ - Requires Git 2.34.0+
98
100
 
99
101
  ## 5. Using SSH Signing in Workflows
100
102
 
@@ -111,7 +113,7 @@ jobs:
111
113
  with:
112
114
  fetch-depth: 0
113
115
 
114
- - uses: ./.github/actions/setup-ssh-bot
116
+ - uses: photostructure/git-ssh-signing-action@v1
115
117
  with:
116
118
  ssh-signing-key: ${{ secrets.SSH_SIGNING_KEY }}
117
119
  git-user-name: ${{ secrets.GIT_USER_NAME }}
@@ -122,8 +124,7 @@ jobs:
122
124
  - run: npm version patch
123
125
  - run: git push origin main --follow-tags
124
126
 
125
- - uses: ./.github/actions/cleanup-ssh-bot
126
- if: always()
127
+ # Note: Cleanup is handled automatically by the action
127
128
  ```
128
129
 
129
130
  ## 6. Testing SSH Signing
@@ -152,14 +153,14 @@ Before triggering a release:
152
153
 
153
154
  ## 8. Advantages of SSH Signing
154
155
 
155
- | Feature | SSH Signing | GPG Signing |
156
- |---------|-------------|-------------|
157
- | Setup complexity | Simple | Complex |
158
- | Key generation | One command | Multiple steps |
159
- | Passphrase handling | Not required | Required |
160
- | Wrapper scripts | Not needed | Required |
161
- | GitHub verification | ✓ Supported | ✓ Supported |
162
- | Maintenance | Minimal | Higher |
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 |
163
164
 
164
165
  ## 9. Security Best Practices
165
166
 
@@ -185,19 +186,22 @@ mv ~/.ssh/photostructure-bot-signing* ~/secure-backup/
185
186
  ## 11. Troubleshooting
186
187
 
187
188
  ### Commits show as "Unverified"
189
+
188
190
  - Ensure the SSH key is added as a **Signing Key** (not Authentication Key)
189
191
  - Verify the email in Git config matches the GitHub account email
190
192
  - Check that the bot account owns the key
191
193
 
192
194
  ### "error: Load key failed"
195
+
193
196
  - Verify SSH_SIGNING_KEY secret contains the complete private key
194
197
  - Check for extra newlines or spaces in the secret
195
198
 
196
199
  ### Permission denied on push
200
+
197
201
  - Ensure bot account has write access to the repository
198
202
 
199
203
  ## References
200
204
 
201
205
  - [GitHub SSH Commit Verification](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#ssh-commit-signature-verification)
202
206
  - [Git SSH Signing Documentation](https://git-scm.com/docs/git-config#Documentation/git-config.txt-gpgformat)
203
- - [GitHub Actions Encrypted Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets)
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