agentvibes 4.2.0 → 4.4.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.
- package/.agentvibes/bmad/bmad-voices.md +69 -69
- package/.agentvibes/config.json +12 -0
- package/.claude/activation-instructions +54 -54
- package/.claude/audio/tracks/README.md +52 -52
- package/.claude/commands/agent-vibes/add.md +21 -21
- package/.claude/commands/agent-vibes/agent-vibes.md +101 -101
- package/.claude/commands/agent-vibes/agent.md +79 -79
- package/.claude/commands/agent-vibes/background-music.md +111 -111
- package/.claude/commands/agent-vibes/bmad.md +198 -198
- package/.claude/commands/agent-vibes/clean.md +18 -18
- package/.claude/commands/agent-vibes/cleanup.md +18 -18
- package/.claude/commands/agent-vibes/commands.json +145 -145
- package/.claude/commands/agent-vibes/effects.md +97 -97
- package/.claude/commands/agent-vibes/get.md +9 -9
- package/.claude/commands/agent-vibes/hide.md +91 -91
- package/.claude/commands/agent-vibes/language.md +23 -23
- package/.claude/commands/agent-vibes/learn.md +67 -67
- package/.claude/commands/agent-vibes/list.md +13 -13
- package/.claude/commands/agent-vibes/mute.md +37 -37
- package/.claude/commands/agent-vibes/preview.md +17 -17
- package/.claude/commands/agent-vibes/provider.md +68 -68
- package/.claude/commands/agent-vibes/replay-target.md +14 -14
- package/.claude/commands/agent-vibes/sample.md +12 -12
- package/.claude/commands/agent-vibes/set-favorite-voice.md +84 -84
- package/.claude/commands/agent-vibes/set-pretext.md +65 -65
- package/.claude/commands/agent-vibes/set-speed.md +41 -41
- package/.claude/commands/agent-vibes/show.md +84 -84
- package/.claude/commands/agent-vibes/switch.md +87 -87
- package/.claude/commands/agent-vibes/target-voice.md +26 -26
- package/.claude/commands/agent-vibes/target.md +30 -30
- package/.claude/commands/agent-vibes/translate.md +68 -68
- package/.claude/commands/agent-vibes/unmute.md +45 -45
- package/.claude/commands/agent-vibes/verbosity.md +89 -89
- package/.claude/commands/agent-vibes/whoami.md +7 -7
- package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
- package/.claude/commands/agent-vibes-rdp.md +24 -24
- package/.claude/config/agentvibes.json +1 -0
- package/.claude/config/audio-effects.cfg +2 -2
- package/.claude/config/audio-effects.cfg.sample +52 -52
- package/.claude/config/background-music-volume.txt +1 -0
- package/.claude/config/intro-text.txt +1 -0
- package/.claude/config/piper-speech-rate.txt +4 -0
- package/.claude/config/piper-target-speech-rate.txt +1 -0
- package/.claude/config/reverb-level.txt +1 -0
- package/.claude/config/tts-speech-rate.txt +4 -0
- package/.claude/config/tts-target-speech-rate.txt +1 -0
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/README-TTS-QUEUE.md +135 -135
- package/.claude/hooks/audio-cache-utils.sh +246 -246
- package/.claude/hooks/audio-processor.sh +433 -433
- package/.claude/hooks/background-music-manager.sh +404 -404
- package/.claude/hooks/bmad-speak-enhanced.sh +165 -165
- package/.claude/hooks/bmad-speak.sh +269 -269
- package/.claude/hooks/bmad-tts-injector.sh +568 -568
- package/.claude/hooks/bmad-voice-manager.sh +928 -928
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +129 -129
- package/.claude/hooks/clawdbot-receiver.sh +107 -107
- package/.claude/hooks/clean-audio-cache.sh +22 -22
- package/.claude/hooks/cleanup-cache.sh +106 -106
- package/.claude/hooks/configure-rdp-mode.sh +137 -137
- package/.claude/hooks/download-extra-voices.sh +244 -244
- package/.claude/hooks/effects-manager.sh +268 -268
- package/.claude/hooks/github-star-reminder.sh +154 -154
- package/.claude/hooks/language-manager.sh +362 -362
- package/.claude/hooks/learn-manager.sh +492 -492
- package/.claude/hooks/macos-voice-manager.sh +205 -205
- package/.claude/hooks/migrate-background-music.sh +125 -125
- package/.claude/hooks/migrate-to-agentvibes.sh +161 -161
- package/.claude/hooks/optimize-background-music.sh +87 -87
- package/.claude/hooks/path-resolver.sh +60 -60
- package/.claude/hooks/personality-manager.sh +448 -448
- package/.claude/hooks/piper-download-voices.sh +225 -225
- package/.claude/hooks/piper-installer.sh +292 -292
- package/.claude/hooks/piper-multispeaker-registry.sh +171 -171
- package/.claude/hooks/piper-voice-manager.sh +24 -3
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +90 -90
- package/.claude/hooks/play-tts-enhanced.sh +105 -105
- package/.claude/hooks/play-tts-macos.sh +368 -368
- package/.claude/hooks/play-tts-piper.sh +679 -679
- package/.claude/hooks/play-tts-soprano.sh +356 -356
- package/.claude/hooks/play-tts-ssh-remote.sh +167 -167
- package/.claude/hooks/play-tts-termux-ssh.sh +169 -169
- package/.claude/hooks/play-tts.sh +301 -301
- package/.claude/hooks/prepare-release.sh +54 -54
- package/.claude/hooks/provider-commands.sh +617 -617
- package/.claude/hooks/provider-manager.sh +399 -399
- package/.claude/hooks/replay-target-audio.sh +95 -95
- package/.claude/hooks/requirements.txt +6 -6
- package/.claude/hooks/sentiment-manager.sh +201 -201
- package/.claude/hooks/session-start-tts.sh +81 -81
- package/.claude/hooks/soprano-gradio-synth.py +139 -139
- package/.claude/hooks/speed-manager.sh +291 -291
- package/.claude/hooks/stop-tts.sh +84 -84
- package/.claude/hooks/termux-installer.sh +261 -261
- package/.claude/hooks/translate-manager.sh +341 -341
- package/.claude/hooks/translator.py +237 -237
- package/.claude/hooks/tts-queue-worker.sh +145 -145
- package/.claude/hooks/tts-queue.sh +165 -165
- package/.claude/hooks/verbosity-manager.sh +178 -178
- package/.claude/hooks/voice-manager.sh +548 -548
- package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
- package/.claude/hooks-windows/background-music-manager.ps1 +348 -0
- package/.claude/hooks-windows/clean-audio-cache.ps1 +53 -0
- package/.claude/hooks-windows/download-extra-voices.ps1 +185 -0
- package/.claude/hooks-windows/effects-manager.ps1 +294 -0
- package/.claude/hooks-windows/language-manager.ps1 +193 -0
- package/.claude/hooks-windows/learn-manager.ps1 +241 -0
- package/.claude/hooks-windows/personality-manager.ps1 +266 -0
- package/.claude/hooks-windows/play-tts-piper.ps1 +209 -0
- package/.claude/hooks-windows/play-tts-sapi.ps1 +108 -0
- package/.claude/hooks-windows/play-tts-soprano.ps1 +159 -158
- package/.claude/hooks-windows/play-tts-windows-piper.ps1 +50 -5
- package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +108 -108
- package/.claude/hooks-windows/play-tts.ps1 +344 -266
- package/.claude/hooks-windows/provider-manager.ps1 +29 -10
- package/.claude/hooks-windows/session-start-tts.ps1 +124 -124
- package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
- package/.claude/hooks-windows/speed-manager.ps1 +166 -0
- package/.claude/hooks-windows/verbosity-manager.ps1 +119 -0
- package/.claude/hooks-windows/voice-manager-windows.ps1 +92 -8
- package/.claude/output-styles/agent-vibes.md +202 -202
- package/.claude/personalities/angry.md +14 -14
- package/.claude/personalities/annoying.md +14 -14
- package/.claude/personalities/crass.md +14 -14
- package/.claude/personalities/dramatic.md +14 -14
- package/.claude/personalities/dry-humor.md +50 -50
- package/.claude/personalities/flirty.md +20 -20
- package/.claude/personalities/funny.md +14 -14
- package/.claude/personalities/grandpa.md +32 -32
- package/.claude/personalities/millennial.md +14 -14
- package/.claude/personalities/moody.md +14 -14
- package/.claude/personalities/normal.md +16 -16
- package/.claude/personalities/pirate.md +14 -14
- package/.claude/personalities/poetic.md +14 -14
- package/.claude/personalities/professional.md +14 -14
- package/.claude/personalities/rapper.md +55 -55
- package/.claude/personalities/robot.md +14 -14
- package/.claude/personalities/sarcastic.md +38 -38
- package/.claude/personalities/sassy.md +14 -14
- package/.claude/personalities/surfer-dude.md +14 -14
- package/.claude/personalities/zen.md +14 -14
- package/.claude/settings.json +15 -15
- package/.claude/verbosity.txt +1 -1
- package/.clawdbot/README.md +105 -105
- package/.clawdbot/skill/SKILL.md +241 -241
- package/.mcp.json +12 -0
- package/CLAUDE.md +170 -170
- package/README.md +2029 -2007
- package/RELEASE_NOTES.md +1310 -1203
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/agentvibes-voice-browser.js +1840 -1840
- package/bin/agentvibes.js +48 -2
- package/bin/mcp-server.js +121 -121
- package/bin/mcp-server.sh +206 -206
- package/bin/test-bmad-pr +78 -78
- package/mcp-server/QUICK_START.md +203 -203
- package/mcp-server/README.md +345 -345
- package/mcp-server/WINDOWS_SETUP.md +260 -260
- package/mcp-server/docs/troubleshooting-audio.md +313 -313
- package/mcp-server/examples/claude_desktop_config.json +11 -11
- package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
- package/mcp-server/examples/custom_instructions.md +169 -169
- package/mcp-server/install-deps.js +130 -130
- package/mcp-server/pyproject.toml +52 -52
- package/mcp-server/requirements.txt +2 -2
- package/mcp-server/server.py +1465 -1453
- package/mcp-server/test_server.py +395 -395
- package/mcp-server/test_windows_script_parity.py +336 -0
- package/package.json +110 -110
- package/setup-windows.ps1 +815 -815
- package/src/bmad-detector.js +71 -71
- package/src/cli/list-personalities.js +110 -110
- package/src/cli/list-voices.js +114 -114
- package/src/commands/bmad-voices.js +394 -394
- package/src/commands/install-mcp.js +476 -476
- package/src/console/app.js +824 -824
- package/src/console/audio-env.js +20 -1
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/footer-config.js +50 -50
- package/src/console/modals/modal-overlay.js +247 -247
- package/src/console/navigation.js +62 -62
- package/src/console/tabs/agents-tab.js +1684 -1516
- package/src/console/tabs/help-tab.js +261 -261
- package/src/console/tabs/install-tab.js +1007 -991
- package/src/console/tabs/music-tab.js +22 -8
- package/src/console/tabs/placeholder-tab.js +53 -53
- package/src/console/tabs/readme-tab.js +267 -267
- package/src/console/tabs/receiver-tab.js +1472 -1212
- package/src/console/tabs/settings-tab.js +152 -79
- package/src/console/tabs/voices-tab.js +100 -21
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/format-utils.js +89 -89
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/personality-picker.js +185 -185
- package/src/console/widgets/reverb-picker.js +94 -94
- package/src/console/widgets/track-picker.js +285 -285
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +5882 -5829
- package/src/services/agent-voice-store.js +423 -423
- package/src/services/config-service.js +264 -264
- package/src/services/navigation-service.js +123 -123
- package/src/services/provider-service.js +132 -132
- package/src/services/verbosity-service.js +157 -157
- package/src/utils/audio-duration-validator.js +298 -298
- package/src/utils/audio-format-validator.js +277 -277
- package/src/utils/dependency-checker.js +469 -466
- package/src/utils/file-ownership-verifier.js +358 -358
- package/src/utils/list-formatter.js +194 -194
- package/src/utils/music-file-validator.js +285 -285
- package/src/utils/preview-list-prompt.js +136 -136
- package/src/utils/provider-validator.js +96 -12
- package/src/utils/secure-music-storage.js +412 -412
- package/templates/agentvibes-receiver.sh +482 -482
- package/templates/audio/welcome-music.mp3 +0 -0
- package/voice-assignments.json +8244 -8244
- package/.claude/config/background-music-position.txt +0 -1
|
@@ -1,358 +1,358 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File Ownership Verifier - Cross-Platform Security Validation
|
|
3
|
-
* Story 4.3: File Ownership Verification
|
|
4
|
-
*
|
|
5
|
-
* Verifies that files are owned by the current user before processing.
|
|
6
|
-
* This prevents malicious files planted by other users from being used.
|
|
7
|
-
*
|
|
8
|
-
* Handles platform-specific differences:
|
|
9
|
-
* - Unix/Linux/macOS: UID-based ownership checking
|
|
10
|
-
* - Windows: Uses fs.statSync().uid if available, graceful fallback
|
|
11
|
-
* - Network mounts: Documented limitation, best-effort checking
|
|
12
|
-
*
|
|
13
|
-
* @module file-ownership-verifier
|
|
14
|
-
* @requires fs
|
|
15
|
-
* @requires os
|
|
16
|
-
* @requires path
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import fs from 'node:fs';
|
|
20
|
-
import os from 'node:os';
|
|
21
|
-
import path from 'node:path';
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Story 4.3: Verify file is owned by current user
|
|
25
|
-
*
|
|
26
|
-
* On Unix-like systems (Linux, macOS):
|
|
27
|
-
* - Compares fs.statSync().uid with process.getuid()
|
|
28
|
-
*
|
|
29
|
-
* On Windows:
|
|
30
|
-
* - Attempts UID comparison if available
|
|
31
|
-
* - Gracefully falls back to path/permission checks if UID unavailable
|
|
32
|
-
* - Network mount files may not have reliable ownership info
|
|
33
|
-
*
|
|
34
|
-
* Performance: Typically < 5ms (stat operation only, no I/O)
|
|
35
|
-
*
|
|
36
|
-
* @param {string} filePath - Path to file to check
|
|
37
|
-
* @param {Object} options - Verification options
|
|
38
|
-
* @param {boolean} options.allowNetworkMounts - Allow verification to succeed on network mounts (default: true)
|
|
39
|
-
* @param {Function} options.logger - Optional logger function for sanitized logs
|
|
40
|
-
* @returns {Object} {
|
|
41
|
-
* isOwned: boolean,
|
|
42
|
-
* error: string|null,
|
|
43
|
-
* ownerUid: number|null,
|
|
44
|
-
* currentUid: number|null,
|
|
45
|
-
* isNetworkMount: boolean,
|
|
46
|
-
* platform: string
|
|
47
|
-
* }
|
|
48
|
-
*/
|
|
49
|
-
export function verifyFileOwnership(filePath, options = {}) {
|
|
50
|
-
const { allowNetworkMounts = true, logger = null } = options;
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
if (!filePath || typeof filePath !== 'string') {
|
|
54
|
-
const error = 'File path must be a non-empty string';
|
|
55
|
-
logger?.(`ownership-check: invalid-path - ${error}`);
|
|
56
|
-
return {
|
|
57
|
-
isOwned: false,
|
|
58
|
-
error,
|
|
59
|
-
ownerUid: null,
|
|
60
|
-
currentUid: null,
|
|
61
|
-
isNetworkMount: false,
|
|
62
|
-
platform: process.platform
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Check file exists
|
|
67
|
-
if (!fs.existsSync(filePath)) {
|
|
68
|
-
const error = 'File does not exist';
|
|
69
|
-
logger?.(`ownership-check: missing-file - ${filePath}`);
|
|
70
|
-
return {
|
|
71
|
-
isOwned: false,
|
|
72
|
-
error,
|
|
73
|
-
ownerUid: null,
|
|
74
|
-
currentUid: null,
|
|
75
|
-
isNetworkMount: false,
|
|
76
|
-
platform: process.platform
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Get file stats
|
|
81
|
-
const stats = fs.statSync(filePath);
|
|
82
|
-
|
|
83
|
-
// Check if it's a regular file
|
|
84
|
-
if (!stats.isFile()) {
|
|
85
|
-
const error = 'Path must be a regular file';
|
|
86
|
-
logger?.(`ownership-check: not-file - ${filePath}`);
|
|
87
|
-
return {
|
|
88
|
-
isOwned: false,
|
|
89
|
-
error,
|
|
90
|
-
ownerUid: null,
|
|
91
|
-
currentUid: null,
|
|
92
|
-
isNetworkMount: false,
|
|
93
|
-
platform: process.platform
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const currentUid = process.getuid ? process.getuid() : null;
|
|
98
|
-
const fileUid = stats.uid;
|
|
99
|
-
|
|
100
|
-
// Determine platform type for logging
|
|
101
|
-
const platformType = getPlatformType();
|
|
102
|
-
const isNetworkMount = checkIsNetworkMount(filePath);
|
|
103
|
-
|
|
104
|
-
// Log verification attempt (sanitized - no sensitive paths)
|
|
105
|
-
logger?.(`ownership-check: started - platform=${platformType}, network=${isNetworkMount}`);
|
|
106
|
-
|
|
107
|
-
// Platform-specific ownership verification
|
|
108
|
-
if (process.platform === 'win32') {
|
|
109
|
-
return verifyWindowsOwnership(filePath, stats, currentUid, isNetworkMount, allowNetworkMounts, logger);
|
|
110
|
-
} else {
|
|
111
|
-
// Unix-like systems (Linux, macOS)
|
|
112
|
-
return verifyUnixOwnership(filePath, stats, currentUid, isNetworkMount, allowNetworkMounts, logger);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
} catch (err) {
|
|
116
|
-
const error = `Error verifying file ownership: ${err.message}`;
|
|
117
|
-
logger?.(`ownership-check: error - ${error}`);
|
|
118
|
-
return {
|
|
119
|
-
isOwned: false,
|
|
120
|
-
error,
|
|
121
|
-
ownerUid: null,
|
|
122
|
-
currentUid: null,
|
|
123
|
-
isNetworkMount: false,
|
|
124
|
-
platform: process.platform
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Unix/Linux/macOS ownership verification
|
|
131
|
-
* Uses UID comparison for reliable security
|
|
132
|
-
*
|
|
133
|
-
* @private
|
|
134
|
-
*/
|
|
135
|
-
function verifyUnixOwnership(filePath, stats, currentUid, isNetworkMount, allowNetworkMounts, logger) {
|
|
136
|
-
if (currentUid === null) {
|
|
137
|
-
const error = 'Unable to determine current user UID (not available on this platform)';
|
|
138
|
-
logger?.(`ownership-check: no-uid-support`);
|
|
139
|
-
return {
|
|
140
|
-
isOwned: false,
|
|
141
|
-
error,
|
|
142
|
-
ownerUid: stats.uid,
|
|
143
|
-
currentUid: null,
|
|
144
|
-
isNetworkMount,
|
|
145
|
-
platform: process.platform
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Check if UIDs match
|
|
150
|
-
if (stats.uid === currentUid) {
|
|
151
|
-
logger?.(`ownership-check: success - uid=${currentUid}`);
|
|
152
|
-
return {
|
|
153
|
-
isOwned: true,
|
|
154
|
-
error: null,
|
|
155
|
-
ownerUid: stats.uid,
|
|
156
|
-
currentUid,
|
|
157
|
-
isNetworkMount,
|
|
158
|
-
platform: process.platform
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// UIDs don't match - file owned by different user
|
|
163
|
-
const error = 'File not owned by current user (security check failed)';
|
|
164
|
-
logger?.(`ownership-check: failed - file-uid=${stats.uid}, user-uid=${currentUid}`);
|
|
165
|
-
return {
|
|
166
|
-
isOwned: false,
|
|
167
|
-
error,
|
|
168
|
-
ownerUid: stats.uid,
|
|
169
|
-
currentUid,
|
|
170
|
-
isNetworkMount,
|
|
171
|
-
platform: process.platform
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Windows ownership verification
|
|
177
|
-
* Windows doesn't have traditional UID system, use file permissions
|
|
178
|
-
*
|
|
179
|
-
* @private
|
|
180
|
-
*/
|
|
181
|
-
function verifyWindowsOwnership(filePath, stats, currentUid, isNetworkMount, allowNetworkMounts, logger) {
|
|
182
|
-
// On Windows, process.getuid() is typically undefined
|
|
183
|
-
// Try to verify through accessible permissions instead
|
|
184
|
-
|
|
185
|
-
// If running in WSL (Windows Subsystem for Linux), we have UID available
|
|
186
|
-
if (currentUid !== null && stats.uid !== undefined) {
|
|
187
|
-
if (stats.uid === currentUid) {
|
|
188
|
-
logger?.(`ownership-check: windows-wsl-success - uid=${currentUid}`);
|
|
189
|
-
return {
|
|
190
|
-
isOwned: true,
|
|
191
|
-
error: null,
|
|
192
|
-
ownerUid: stats.uid,
|
|
193
|
-
currentUid,
|
|
194
|
-
isNetworkMount,
|
|
195
|
-
platform: process.platform
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const error = 'File not owned by current user (security check failed)';
|
|
200
|
-
logger?.(`ownership-check: windows-wsl-failed - file-uid=${stats.uid}, user-uid=${currentUid}`);
|
|
201
|
-
return {
|
|
202
|
-
isOwned: false,
|
|
203
|
-
error,
|
|
204
|
-
ownerUid: stats.uid,
|
|
205
|
-
currentUid,
|
|
206
|
-
isNetworkMount,
|
|
207
|
-
platform: process.platform
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Native Windows without UID support
|
|
212
|
-
// Check if file is readable and writable by current user (best effort)
|
|
213
|
-
try {
|
|
214
|
-
// Try to read and write to verify we own it
|
|
215
|
-
fs.accessSync(filePath, fs.constants.R_OK | fs.constants.W_OK);
|
|
216
|
-
|
|
217
|
-
// If we can read and write, assume we own it
|
|
218
|
-
logger?.(`ownership-check: windows-native-success - readable-writable`);
|
|
219
|
-
return {
|
|
220
|
-
isOwned: true,
|
|
221
|
-
error: null,
|
|
222
|
-
ownerUid: null,
|
|
223
|
-
currentUid: null,
|
|
224
|
-
isNetworkMount,
|
|
225
|
-
platform: process.platform
|
|
226
|
-
};
|
|
227
|
-
} catch (err) {
|
|
228
|
-
// Can't read/write - likely not owned by us or permission issue
|
|
229
|
-
const error = `File not owned by current user or not accessible (Windows): ${err.message}`;
|
|
230
|
-
logger?.(`ownership-check: windows-native-failed - ${err.code}`);
|
|
231
|
-
return {
|
|
232
|
-
isOwned: false,
|
|
233
|
-
error,
|
|
234
|
-
ownerUid: null,
|
|
235
|
-
currentUid: null,
|
|
236
|
-
isNetworkMount,
|
|
237
|
-
platform: process.platform
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Determine if path is likely a network mount
|
|
244
|
-
* Used to set expectations for ownership checking reliability
|
|
245
|
-
*
|
|
246
|
-
* @private
|
|
247
|
-
*/
|
|
248
|
-
function checkIsNetworkMount(filePath) {
|
|
249
|
-
const resolvedPath = path.resolve(filePath);
|
|
250
|
-
|
|
251
|
-
if (process.platform === 'win32') {
|
|
252
|
-
// Windows: check for UNC paths (\\server\share) or mapped drives
|
|
253
|
-
// Also check for paths under %APPDATA% or similar which might be network-synced
|
|
254
|
-
return resolvedPath.startsWith('\\\\') ||
|
|
255
|
-
resolvedPath.match(/^[A-Z]:\\[^\\]*\\netshare/i);
|
|
256
|
-
} else {
|
|
257
|
-
// Unix: check for common network mount prefixes
|
|
258
|
-
// /mnt, /media, NFS mount points
|
|
259
|
-
return resolvedPath.startsWith('/mnt/') ||
|
|
260
|
-
resolvedPath.startsWith('/media/') ||
|
|
261
|
-
resolvedPath.includes(':/'); // NFS mount indicators
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Get human-readable platform type
|
|
267
|
-
*
|
|
268
|
-
* @private
|
|
269
|
-
*/
|
|
270
|
-
function getPlatformType() {
|
|
271
|
-
switch (process.platform) {
|
|
272
|
-
case 'linux':
|
|
273
|
-
return 'linux';
|
|
274
|
-
case 'darwin':
|
|
275
|
-
return 'macos';
|
|
276
|
-
case 'win32':
|
|
277
|
-
return 'windows';
|
|
278
|
-
default:
|
|
279
|
-
return process.platform;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Story 4.3: Get current user information for logging/debugging
|
|
285
|
-
*
|
|
286
|
-
* Returns user info in a sanitized format safe for logging.
|
|
287
|
-
* Does NOT include sensitive paths or full usernames.
|
|
288
|
-
*
|
|
289
|
-
* @returns {Object} {
|
|
290
|
-
* uid: number|null,
|
|
291
|
-
* gid: number|null,
|
|
292
|
-
* username: string,
|
|
293
|
-
* platform: string
|
|
294
|
-
* }
|
|
295
|
-
*/
|
|
296
|
-
export function getCurrentUserInfo() {
|
|
297
|
-
try {
|
|
298
|
-
const uid = process.getuid ? process.getuid() : null;
|
|
299
|
-
const gid = process.getgid ? process.getgid() : null;
|
|
300
|
-
const username = os.userInfo().username || 'unknown';
|
|
301
|
-
|
|
302
|
-
return {
|
|
303
|
-
uid,
|
|
304
|
-
gid,
|
|
305
|
-
username,
|
|
306
|
-
platform: getPlatformType()
|
|
307
|
-
};
|
|
308
|
-
} catch (err) {
|
|
309
|
-
return {
|
|
310
|
-
uid: null,
|
|
311
|
-
gid: null,
|
|
312
|
-
username: 'unknown',
|
|
313
|
-
platform: getPlatformType()
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Story 4.3: Batch verify ownership of multiple files
|
|
320
|
-
*
|
|
321
|
-
* Efficiently checks ownership of multiple files.
|
|
322
|
-
* Returns detailed results for each file.
|
|
323
|
-
*
|
|
324
|
-
* @param {string[]} filePaths - Array of file paths to check
|
|
325
|
-
* @param {Object} options - Verification options (passed to verifyFileOwnership)
|
|
326
|
-
* @returns {Object} {
|
|
327
|
-
* allOwned: boolean,
|
|
328
|
-
* results: Array<{path: string, isOwned: boolean, error: string|null}>
|
|
329
|
-
* }
|
|
330
|
-
*/
|
|
331
|
-
export function verifyMultipleFiles(filePaths, options = {}) {
|
|
332
|
-
const results = [];
|
|
333
|
-
let allOwned = true;
|
|
334
|
-
|
|
335
|
-
for (const filePath of filePaths) {
|
|
336
|
-
const result = verifyFileOwnership(filePath, options);
|
|
337
|
-
results.push({
|
|
338
|
-
path: filePath,
|
|
339
|
-
isOwned: result.isOwned,
|
|
340
|
-
error: result.error
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
if (!result.isOwned) {
|
|
344
|
-
allOwned = false;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
return {
|
|
349
|
-
allOwned,
|
|
350
|
-
results
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
export default {
|
|
355
|
-
verifyFileOwnership,
|
|
356
|
-
getCurrentUserInfo,
|
|
357
|
-
verifyMultipleFiles
|
|
358
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* File Ownership Verifier - Cross-Platform Security Validation
|
|
3
|
+
* Story 4.3: File Ownership Verification
|
|
4
|
+
*
|
|
5
|
+
* Verifies that files are owned by the current user before processing.
|
|
6
|
+
* This prevents malicious files planted by other users from being used.
|
|
7
|
+
*
|
|
8
|
+
* Handles platform-specific differences:
|
|
9
|
+
* - Unix/Linux/macOS: UID-based ownership checking
|
|
10
|
+
* - Windows: Uses fs.statSync().uid if available, graceful fallback
|
|
11
|
+
* - Network mounts: Documented limitation, best-effort checking
|
|
12
|
+
*
|
|
13
|
+
* @module file-ownership-verifier
|
|
14
|
+
* @requires fs
|
|
15
|
+
* @requires os
|
|
16
|
+
* @requires path
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import os from 'node:os';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Story 4.3: Verify file is owned by current user
|
|
25
|
+
*
|
|
26
|
+
* On Unix-like systems (Linux, macOS):
|
|
27
|
+
* - Compares fs.statSync().uid with process.getuid()
|
|
28
|
+
*
|
|
29
|
+
* On Windows:
|
|
30
|
+
* - Attempts UID comparison if available
|
|
31
|
+
* - Gracefully falls back to path/permission checks if UID unavailable
|
|
32
|
+
* - Network mount files may not have reliable ownership info
|
|
33
|
+
*
|
|
34
|
+
* Performance: Typically < 5ms (stat operation only, no I/O)
|
|
35
|
+
*
|
|
36
|
+
* @param {string} filePath - Path to file to check
|
|
37
|
+
* @param {Object} options - Verification options
|
|
38
|
+
* @param {boolean} options.allowNetworkMounts - Allow verification to succeed on network mounts (default: true)
|
|
39
|
+
* @param {Function} options.logger - Optional logger function for sanitized logs
|
|
40
|
+
* @returns {Object} {
|
|
41
|
+
* isOwned: boolean,
|
|
42
|
+
* error: string|null,
|
|
43
|
+
* ownerUid: number|null,
|
|
44
|
+
* currentUid: number|null,
|
|
45
|
+
* isNetworkMount: boolean,
|
|
46
|
+
* platform: string
|
|
47
|
+
* }
|
|
48
|
+
*/
|
|
49
|
+
export function verifyFileOwnership(filePath, options = {}) {
|
|
50
|
+
const { allowNetworkMounts = true, logger = null } = options;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
54
|
+
const error = 'File path must be a non-empty string';
|
|
55
|
+
logger?.(`ownership-check: invalid-path - ${error}`);
|
|
56
|
+
return {
|
|
57
|
+
isOwned: false,
|
|
58
|
+
error,
|
|
59
|
+
ownerUid: null,
|
|
60
|
+
currentUid: null,
|
|
61
|
+
isNetworkMount: false,
|
|
62
|
+
platform: process.platform
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check file exists
|
|
67
|
+
if (!fs.existsSync(filePath)) {
|
|
68
|
+
const error = 'File does not exist';
|
|
69
|
+
logger?.(`ownership-check: missing-file - ${filePath}`);
|
|
70
|
+
return {
|
|
71
|
+
isOwned: false,
|
|
72
|
+
error,
|
|
73
|
+
ownerUid: null,
|
|
74
|
+
currentUid: null,
|
|
75
|
+
isNetworkMount: false,
|
|
76
|
+
platform: process.platform
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Get file stats
|
|
81
|
+
const stats = fs.statSync(filePath);
|
|
82
|
+
|
|
83
|
+
// Check if it's a regular file
|
|
84
|
+
if (!stats.isFile()) {
|
|
85
|
+
const error = 'Path must be a regular file';
|
|
86
|
+
logger?.(`ownership-check: not-file - ${filePath}`);
|
|
87
|
+
return {
|
|
88
|
+
isOwned: false,
|
|
89
|
+
error,
|
|
90
|
+
ownerUid: null,
|
|
91
|
+
currentUid: null,
|
|
92
|
+
isNetworkMount: false,
|
|
93
|
+
platform: process.platform
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const currentUid = process.getuid ? process.getuid() : null;
|
|
98
|
+
const fileUid = stats.uid;
|
|
99
|
+
|
|
100
|
+
// Determine platform type for logging
|
|
101
|
+
const platformType = getPlatformType();
|
|
102
|
+
const isNetworkMount = checkIsNetworkMount(filePath);
|
|
103
|
+
|
|
104
|
+
// Log verification attempt (sanitized - no sensitive paths)
|
|
105
|
+
logger?.(`ownership-check: started - platform=${platformType}, network=${isNetworkMount}`);
|
|
106
|
+
|
|
107
|
+
// Platform-specific ownership verification
|
|
108
|
+
if (process.platform === 'win32') {
|
|
109
|
+
return verifyWindowsOwnership(filePath, stats, currentUid, isNetworkMount, allowNetworkMounts, logger);
|
|
110
|
+
} else {
|
|
111
|
+
// Unix-like systems (Linux, macOS)
|
|
112
|
+
return verifyUnixOwnership(filePath, stats, currentUid, isNetworkMount, allowNetworkMounts, logger);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
} catch (err) {
|
|
116
|
+
const error = `Error verifying file ownership: ${err.message}`;
|
|
117
|
+
logger?.(`ownership-check: error - ${error}`);
|
|
118
|
+
return {
|
|
119
|
+
isOwned: false,
|
|
120
|
+
error,
|
|
121
|
+
ownerUid: null,
|
|
122
|
+
currentUid: null,
|
|
123
|
+
isNetworkMount: false,
|
|
124
|
+
platform: process.platform
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Unix/Linux/macOS ownership verification
|
|
131
|
+
* Uses UID comparison for reliable security
|
|
132
|
+
*
|
|
133
|
+
* @private
|
|
134
|
+
*/
|
|
135
|
+
function verifyUnixOwnership(filePath, stats, currentUid, isNetworkMount, allowNetworkMounts, logger) {
|
|
136
|
+
if (currentUid === null) {
|
|
137
|
+
const error = 'Unable to determine current user UID (not available on this platform)';
|
|
138
|
+
logger?.(`ownership-check: no-uid-support`);
|
|
139
|
+
return {
|
|
140
|
+
isOwned: false,
|
|
141
|
+
error,
|
|
142
|
+
ownerUid: stats.uid,
|
|
143
|
+
currentUid: null,
|
|
144
|
+
isNetworkMount,
|
|
145
|
+
platform: process.platform
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Check if UIDs match
|
|
150
|
+
if (stats.uid === currentUid) {
|
|
151
|
+
logger?.(`ownership-check: success - uid=${currentUid}`);
|
|
152
|
+
return {
|
|
153
|
+
isOwned: true,
|
|
154
|
+
error: null,
|
|
155
|
+
ownerUid: stats.uid,
|
|
156
|
+
currentUid,
|
|
157
|
+
isNetworkMount,
|
|
158
|
+
platform: process.platform
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// UIDs don't match - file owned by different user
|
|
163
|
+
const error = 'File not owned by current user (security check failed)';
|
|
164
|
+
logger?.(`ownership-check: failed - file-uid=${stats.uid}, user-uid=${currentUid}`);
|
|
165
|
+
return {
|
|
166
|
+
isOwned: false,
|
|
167
|
+
error,
|
|
168
|
+
ownerUid: stats.uid,
|
|
169
|
+
currentUid,
|
|
170
|
+
isNetworkMount,
|
|
171
|
+
platform: process.platform
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Windows ownership verification
|
|
177
|
+
* Windows doesn't have traditional UID system, use file permissions
|
|
178
|
+
*
|
|
179
|
+
* @private
|
|
180
|
+
*/
|
|
181
|
+
function verifyWindowsOwnership(filePath, stats, currentUid, isNetworkMount, allowNetworkMounts, logger) {
|
|
182
|
+
// On Windows, process.getuid() is typically undefined
|
|
183
|
+
// Try to verify through accessible permissions instead
|
|
184
|
+
|
|
185
|
+
// If running in WSL (Windows Subsystem for Linux), we have UID available
|
|
186
|
+
if (currentUid !== null && stats.uid !== undefined) {
|
|
187
|
+
if (stats.uid === currentUid) {
|
|
188
|
+
logger?.(`ownership-check: windows-wsl-success - uid=${currentUid}`);
|
|
189
|
+
return {
|
|
190
|
+
isOwned: true,
|
|
191
|
+
error: null,
|
|
192
|
+
ownerUid: stats.uid,
|
|
193
|
+
currentUid,
|
|
194
|
+
isNetworkMount,
|
|
195
|
+
platform: process.platform
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const error = 'File not owned by current user (security check failed)';
|
|
200
|
+
logger?.(`ownership-check: windows-wsl-failed - file-uid=${stats.uid}, user-uid=${currentUid}`);
|
|
201
|
+
return {
|
|
202
|
+
isOwned: false,
|
|
203
|
+
error,
|
|
204
|
+
ownerUid: stats.uid,
|
|
205
|
+
currentUid,
|
|
206
|
+
isNetworkMount,
|
|
207
|
+
platform: process.platform
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Native Windows without UID support
|
|
212
|
+
// Check if file is readable and writable by current user (best effort)
|
|
213
|
+
try {
|
|
214
|
+
// Try to read and write to verify we own it
|
|
215
|
+
fs.accessSync(filePath, fs.constants.R_OK | fs.constants.W_OK);
|
|
216
|
+
|
|
217
|
+
// If we can read and write, assume we own it
|
|
218
|
+
logger?.(`ownership-check: windows-native-success - readable-writable`);
|
|
219
|
+
return {
|
|
220
|
+
isOwned: true,
|
|
221
|
+
error: null,
|
|
222
|
+
ownerUid: null,
|
|
223
|
+
currentUid: null,
|
|
224
|
+
isNetworkMount,
|
|
225
|
+
platform: process.platform
|
|
226
|
+
};
|
|
227
|
+
} catch (err) {
|
|
228
|
+
// Can't read/write - likely not owned by us or permission issue
|
|
229
|
+
const error = `File not owned by current user or not accessible (Windows): ${err.message}`;
|
|
230
|
+
logger?.(`ownership-check: windows-native-failed - ${err.code}`);
|
|
231
|
+
return {
|
|
232
|
+
isOwned: false,
|
|
233
|
+
error,
|
|
234
|
+
ownerUid: null,
|
|
235
|
+
currentUid: null,
|
|
236
|
+
isNetworkMount,
|
|
237
|
+
platform: process.platform
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Determine if path is likely a network mount
|
|
244
|
+
* Used to set expectations for ownership checking reliability
|
|
245
|
+
*
|
|
246
|
+
* @private
|
|
247
|
+
*/
|
|
248
|
+
function checkIsNetworkMount(filePath) {
|
|
249
|
+
const resolvedPath = path.resolve(filePath);
|
|
250
|
+
|
|
251
|
+
if (process.platform === 'win32') {
|
|
252
|
+
// Windows: check for UNC paths (\\server\share) or mapped drives
|
|
253
|
+
// Also check for paths under %APPDATA% or similar which might be network-synced
|
|
254
|
+
return resolvedPath.startsWith('\\\\') ||
|
|
255
|
+
resolvedPath.match(/^[A-Z]:\\[^\\]*\\netshare/i);
|
|
256
|
+
} else {
|
|
257
|
+
// Unix: check for common network mount prefixes
|
|
258
|
+
// /mnt, /media, NFS mount points
|
|
259
|
+
return resolvedPath.startsWith('/mnt/') ||
|
|
260
|
+
resolvedPath.startsWith('/media/') ||
|
|
261
|
+
resolvedPath.includes(':/'); // NFS mount indicators
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get human-readable platform type
|
|
267
|
+
*
|
|
268
|
+
* @private
|
|
269
|
+
*/
|
|
270
|
+
function getPlatformType() {
|
|
271
|
+
switch (process.platform) {
|
|
272
|
+
case 'linux':
|
|
273
|
+
return 'linux';
|
|
274
|
+
case 'darwin':
|
|
275
|
+
return 'macos';
|
|
276
|
+
case 'win32':
|
|
277
|
+
return 'windows';
|
|
278
|
+
default:
|
|
279
|
+
return process.platform;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Story 4.3: Get current user information for logging/debugging
|
|
285
|
+
*
|
|
286
|
+
* Returns user info in a sanitized format safe for logging.
|
|
287
|
+
* Does NOT include sensitive paths or full usernames.
|
|
288
|
+
*
|
|
289
|
+
* @returns {Object} {
|
|
290
|
+
* uid: number|null,
|
|
291
|
+
* gid: number|null,
|
|
292
|
+
* username: string,
|
|
293
|
+
* platform: string
|
|
294
|
+
* }
|
|
295
|
+
*/
|
|
296
|
+
export function getCurrentUserInfo() {
|
|
297
|
+
try {
|
|
298
|
+
const uid = process.getuid ? process.getuid() : null;
|
|
299
|
+
const gid = process.getgid ? process.getgid() : null;
|
|
300
|
+
const username = os.userInfo().username || 'unknown';
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
uid,
|
|
304
|
+
gid,
|
|
305
|
+
username,
|
|
306
|
+
platform: getPlatformType()
|
|
307
|
+
};
|
|
308
|
+
} catch (err) {
|
|
309
|
+
return {
|
|
310
|
+
uid: null,
|
|
311
|
+
gid: null,
|
|
312
|
+
username: 'unknown',
|
|
313
|
+
platform: getPlatformType()
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Story 4.3: Batch verify ownership of multiple files
|
|
320
|
+
*
|
|
321
|
+
* Efficiently checks ownership of multiple files.
|
|
322
|
+
* Returns detailed results for each file.
|
|
323
|
+
*
|
|
324
|
+
* @param {string[]} filePaths - Array of file paths to check
|
|
325
|
+
* @param {Object} options - Verification options (passed to verifyFileOwnership)
|
|
326
|
+
* @returns {Object} {
|
|
327
|
+
* allOwned: boolean,
|
|
328
|
+
* results: Array<{path: string, isOwned: boolean, error: string|null}>
|
|
329
|
+
* }
|
|
330
|
+
*/
|
|
331
|
+
export function verifyMultipleFiles(filePaths, options = {}) {
|
|
332
|
+
const results = [];
|
|
333
|
+
let allOwned = true;
|
|
334
|
+
|
|
335
|
+
for (const filePath of filePaths) {
|
|
336
|
+
const result = verifyFileOwnership(filePath, options);
|
|
337
|
+
results.push({
|
|
338
|
+
path: filePath,
|
|
339
|
+
isOwned: result.isOwned,
|
|
340
|
+
error: result.error
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
if (!result.isOwned) {
|
|
344
|
+
allOwned = false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
allOwned,
|
|
350
|
+
results
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export default {
|
|
355
|
+
verifyFileOwnership,
|
|
356
|
+
getCurrentUserInfo,
|
|
357
|
+
verifyMultipleFiles
|
|
358
|
+
};
|