gsd-opencode 1.9.2 → 1.10.2
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/agents/gsd-debugger.md +5 -5
- package/agents/gsd-settings.md +476 -30
- package/bin/gsd-install.js +105 -0
- package/bin/gsd.js +352 -0
- package/{command → commands}/gsd/add-phase.md +1 -1
- package/{command → commands}/gsd/audit-milestone.md +1 -1
- package/{command → commands}/gsd/debug.md +3 -3
- package/{command → commands}/gsd/discuss-phase.md +1 -1
- package/{command → commands}/gsd/execute-phase.md +1 -1
- package/{command → commands}/gsd/list-phase-assumptions.md +1 -1
- package/{command → commands}/gsd/map-codebase.md +1 -1
- package/{command → commands}/gsd/new-milestone.md +1 -1
- package/{command → commands}/gsd/new-project.md +3 -3
- package/{command → commands}/gsd/plan-phase.md +2 -2
- package/{command → commands}/gsd/research-phase.md +1 -1
- package/{command → commands}/gsd/verify-work.md +1 -1
- package/get-shit-done/workflows/list-phase-assumptions.md +1 -1
- package/get-shit-done/workflows/verify-work.md +5 -5
- package/lib/constants.js +199 -0
- package/package.json +34 -20
- package/src/commands/check.js +329 -0
- package/src/commands/config.js +337 -0
- package/src/commands/install.js +608 -0
- package/src/commands/list.js +256 -0
- package/src/commands/repair.js +519 -0
- package/src/commands/uninstall.js +732 -0
- package/src/commands/update.js +444 -0
- package/src/services/backup-manager.js +585 -0
- package/src/services/config.js +262 -0
- package/src/services/file-ops.js +855 -0
- package/src/services/health-checker.js +475 -0
- package/src/services/manifest-manager.js +301 -0
- package/src/services/migration-service.js +831 -0
- package/src/services/repair-service.js +846 -0
- package/src/services/scope-manager.js +303 -0
- package/src/services/settings.js +553 -0
- package/src/services/structure-detector.js +240 -0
- package/src/services/update-service.js +863 -0
- package/src/utils/hash.js +71 -0
- package/src/utils/interactive.js +222 -0
- package/src/utils/logger.js +128 -0
- package/src/utils/npm-registry.js +255 -0
- package/src/utils/path-resolver.js +226 -0
- /package/{command → commands}/gsd/add-todo.md +0 -0
- /package/{command → commands}/gsd/check-todos.md +0 -0
- /package/{command → commands}/gsd/complete-milestone.md +0 -0
- /package/{command → commands}/gsd/help.md +0 -0
- /package/{command → commands}/gsd/insert-phase.md +0 -0
- /package/{command → commands}/gsd/pause-work.md +0 -0
- /package/{command → commands}/gsd/plan-milestone-gaps.md +0 -0
- /package/{command → commands}/gsd/progress.md +0 -0
- /package/{command → commands}/gsd/quick.md +0 -0
- /package/{command → commands}/gsd/remove-phase.md +0 -0
- /package/{command → commands}/gsd/resume-work.md +0 -0
- /package/{command → commands}/gsd/set-model.md +0 -0
- /package/{command → commands}/gsd/set-profile.md +0 -0
- /package/{command → commands}/gsd/settings.md +0 -0
- /package/{command → commands}/gsd/update.md +0 -0
- /package/{command → commands}/gsd/whats-new.md +0 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path resolver utility with security-focused traversal protection.
|
|
3
|
+
*
|
|
4
|
+
* This module provides safe path resolution functions that prevent directory
|
|
5
|
+
* traversal attacks. All functions normalize paths and validate that resolved
|
|
6
|
+
* paths remain within allowed directory boundaries.
|
|
7
|
+
*
|
|
8
|
+
* SECURITY NOTE: Always validate paths before using them in file operations.
|
|
9
|
+
* The validatePath() function throws descriptive errors for traversal attempts.
|
|
10
|
+
*
|
|
11
|
+
* @module path-resolver
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import os from 'os';
|
|
16
|
+
import fs from 'fs';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Expand a path string, resolving ~ to home directory and relative paths.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} pathStr - Path string to expand (may contain ~ or relative segments)
|
|
22
|
+
* @returns {string} Absolute, normalized path
|
|
23
|
+
* @throws {Error} If path contains null bytes (potential injection attack)
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* expandPath('~/.config/opencode')
|
|
27
|
+
* // Returns: '/Users/username/.config/opencode' (macOS)
|
|
28
|
+
*
|
|
29
|
+
* expandPath('./relative/path')
|
|
30
|
+
* // Returns: '/absolute/path/to/relative/path'
|
|
31
|
+
*/
|
|
32
|
+
export function expandPath(pathStr) {
|
|
33
|
+
if (typeof pathStr !== 'string') {
|
|
34
|
+
throw new Error('Path must be a string');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Security: Reject null bytes which could be used for injection attacks
|
|
38
|
+
if (pathStr.includes('\0')) {
|
|
39
|
+
throw new Error('Path contains null bytes');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let expanded = pathStr;
|
|
43
|
+
|
|
44
|
+
// Expand ~ to home directory
|
|
45
|
+
if (expanded.startsWith('~')) {
|
|
46
|
+
expanded = expanded.replace('~', os.homedir());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Resolve to absolute path
|
|
50
|
+
const resolved = path.resolve(expanded);
|
|
51
|
+
|
|
52
|
+
// Normalize to handle .. and . segments consistently
|
|
53
|
+
return normalizePath(resolved);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Normalize a path consistently across platforms.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} pathStr - Path to normalize
|
|
60
|
+
* @returns {string} Normalized path with consistent separators
|
|
61
|
+
*/
|
|
62
|
+
export function normalizePath(pathStr) {
|
|
63
|
+
if (typeof pathStr !== 'string') {
|
|
64
|
+
throw new Error('Path must be a string');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Use path.normalize() to resolve .. and . segments
|
|
68
|
+
// This ensures consistent path representation across platforms
|
|
69
|
+
return path.normalize(pathStr);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if a child path is within a parent path.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} childPath - Path to check (child candidate)
|
|
76
|
+
* @param {string} parentPath - Parent directory path
|
|
77
|
+
* @returns {boolean} True if child is within or equal to parent
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* isSubPath('/home/user/.config', '/home/user') // true
|
|
81
|
+
* isSubPath('/home/user', '/home/user') // true
|
|
82
|
+
* isSubPath('/etc/passwd', '/home/user') // false
|
|
83
|
+
*/
|
|
84
|
+
export function isSubPath(childPath, parentPath) {
|
|
85
|
+
if (typeof childPath !== 'string' || typeof parentPath !== 'string') {
|
|
86
|
+
throw new Error('Paths must be strings');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Normalize both paths to ensure consistent comparison
|
|
90
|
+
const normalizedChild = normalizePath(childPath);
|
|
91
|
+
const normalizedParent = normalizePath(parentPath);
|
|
92
|
+
|
|
93
|
+
// Get relative path from parent to child
|
|
94
|
+
const relative = path.relative(normalizedParent, normalizedChild);
|
|
95
|
+
|
|
96
|
+
// If relative path starts with '..', child is outside parent
|
|
97
|
+
// Empty string means same path
|
|
98
|
+
// Path starting without '..' means child is inside or equal to parent
|
|
99
|
+
if (relative === '') {
|
|
100
|
+
return true; // Same path
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// On Windows, relative paths don't use .. for same-level directories
|
|
104
|
+
// Check if relative path contains '..' at the start
|
|
105
|
+
return !relative.startsWith('..');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Validate that a target path does not escape an allowed base directory.
|
|
110
|
+
*
|
|
111
|
+
* SECURITY CRITICAL: This function must be called before any file operation
|
|
112
|
+
* that uses user-provided paths. It prevents directory traversal attacks
|
|
113
|
+
* where malicious input like '../../../etc/passwd' attempts to access
|
|
114
|
+
* files outside the intended directory.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} targetPath - Path to validate
|
|
117
|
+
* @param {string} allowedBasePath - Base directory the target must remain within
|
|
118
|
+
* @returns {string} The resolved, validated absolute path
|
|
119
|
+
* @throws {Error} If path escapes allowed base directory (traversal detected)
|
|
120
|
+
* @throws {Error} If path contains null bytes
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* validatePath('/home/user/.config/opencode', '/home/user') // OK
|
|
124
|
+
* validatePath('/etc/passwd', '/home/user') // Throws error
|
|
125
|
+
*
|
|
126
|
+
* // Handles symlink resolution
|
|
127
|
+
* const realTarget = fs.realpathSync(targetPath);
|
|
128
|
+
* validatePath(realTarget, allowedBasePath);
|
|
129
|
+
*/
|
|
130
|
+
export function validatePath(targetPath, allowedBasePath) {
|
|
131
|
+
if (typeof targetPath !== 'string' || typeof allowedBasePath !== 'string') {
|
|
132
|
+
throw new Error('Paths must be strings');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Security: Reject null bytes
|
|
136
|
+
if (targetPath.includes('\0')) {
|
|
137
|
+
throw new Error('Path contains null bytes');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Expand and normalize both paths
|
|
141
|
+
const resolvedTarget = expandPath(targetPath);
|
|
142
|
+
const resolvedBase = expandPath(allowedBasePath);
|
|
143
|
+
|
|
144
|
+
// Check if target is within allowed base
|
|
145
|
+
if (!isSubPath(resolvedTarget, resolvedBase)) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
'Path traversal detected. Use absolute or relative paths within allowed directories.'
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return resolvedTarget;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Validate a path after resolving symlinks.
|
|
156
|
+
*
|
|
157
|
+
* Symlinks can be used to bypass directory restrictions by pointing to
|
|
158
|
+
* locations outside the allowed base. This function resolves symlinks
|
|
159
|
+
* before validation to ensure the actual file location is checked.
|
|
160
|
+
*
|
|
161
|
+
* @param {string} targetPath - Path to validate (may contain symlinks)
|
|
162
|
+
* @param {string} allowedBasePath - Base directory the target must remain within
|
|
163
|
+
* @returns {Promise<string>} The real, resolved absolute path
|
|
164
|
+
* @throws {Error} If path escapes allowed base directory after symlink resolution
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* // If ~/.config is a symlink to /etc/config:
|
|
168
|
+
* await validatePathSafe('~/.config/opencode', os.homedir());
|
|
169
|
+
* // Throws error because resolved path is outside homedir
|
|
170
|
+
*/
|
|
171
|
+
export async function validatePathSafe(targetPath, allowedBasePath) {
|
|
172
|
+
const expandedTarget = expandPath(targetPath);
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
// Resolve symlinks to get the real path
|
|
176
|
+
const realTarget = await fs.promises.realpath(expandedTarget);
|
|
177
|
+
|
|
178
|
+
// Now validate the resolved path
|
|
179
|
+
return validatePath(realTarget, allowedBasePath);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
// If realpath fails (file doesn't exist), validate the non-resolved path
|
|
182
|
+
// This allows validation of paths that will be created
|
|
183
|
+
if (error.code === 'ENOENT') {
|
|
184
|
+
return validatePath(expandedTarget, allowedBasePath);
|
|
185
|
+
}
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Synchronous version of validatePathSafe.
|
|
192
|
+
*
|
|
193
|
+
* @param {string} targetPath - Path to validate (may contain symlinks)
|
|
194
|
+
* @param {string} allowedBasePath - Base directory the target must remain within
|
|
195
|
+
* @returns {string} The real, resolved absolute path
|
|
196
|
+
* @throws {Error} If path escapes allowed base directory after symlink resolution
|
|
197
|
+
*/
|
|
198
|
+
export function validatePathSafeSync(targetPath, allowedBasePath) {
|
|
199
|
+
const expandedTarget = expandPath(targetPath);
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
// Resolve symlinks synchronously
|
|
203
|
+
const realTarget = fs.realpathSync(expandedTarget);
|
|
204
|
+
|
|
205
|
+
// Now validate the resolved path
|
|
206
|
+
return validatePath(realTarget, allowedBasePath);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
// If realpath fails (file doesn't exist), validate the non-resolved path
|
|
209
|
+
if (error.code === 'ENOENT') {
|
|
210
|
+
return validatePath(expandedTarget, allowedBasePath);
|
|
211
|
+
}
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Default export combining all path resolver functions.
|
|
218
|
+
*/
|
|
219
|
+
export default {
|
|
220
|
+
expandPath,
|
|
221
|
+
normalizePath,
|
|
222
|
+
isSubPath,
|
|
223
|
+
validatePath,
|
|
224
|
+
validatePathSafe,
|
|
225
|
+
validatePathSafeSync
|
|
226
|
+
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|