LoadStrategy loads a strategy from the given name. The resolved path is validated to stay within the strategy directory to prevent path traversal.
(filename string)
| 183 | // LoadStrategy loads a strategy from the given name. |
| 184 | // The resolved path is validated to stay within the strategy directory to prevent path traversal. |
| 185 | func LoadStrategy(filename string) (*Strategy, error) { |
| 186 | if filename == "" { |
| 187 | return nil, nil |
| 188 | } |
| 189 | |
| 190 | // Get the strategy directory path |
| 191 | strategyDir, err := getStrategyDir() |
| 192 | if err != nil { |
| 193 | return nil, err |
| 194 | } |
| 195 | |
| 196 | // First try with .json extension |
| 197 | strategyPath := filepath.Join(strategyDir, filename+".json") |
| 198 | if _, err := os.Stat(strategyPath); os.IsNotExist(err) { |
| 199 | // Try without extension |
| 200 | strategyPath = filepath.Join(strategyDir, filename) |
| 201 | if _, err := os.Stat(strategyPath); os.IsNotExist(err) { |
| 202 | return nil, fmt.Errorf(i18n.T("strategy_not_found"), filename) |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | // Validate the resolved path stays within strategyDir to prevent path traversal |
| 207 | cleanedPath := filepath.Clean(strategyPath) |
| 208 | cleanedDir := filepath.Clean(strategyDir) + string(os.PathSeparator) |
| 209 | if !strings.HasPrefix(cleanedPath, cleanedDir) { |
| 210 | return nil, fmt.Errorf(i18n.T("strategy_path_traversal"), filename) |
| 211 | } |
| 212 | |
| 213 | data, err := os.ReadFile(strategyPath) |
| 214 | if err != nil { |
| 215 | return nil, err |
| 216 | } |
| 217 | |
| 218 | var strategy Strategy |
| 219 | if err := json.Unmarshal(data, &strategy); err != nil { |
| 220 | return nil, err |
| 221 | } |
| 222 | strategy.Name = strings.TrimSuffix(filepath.Base(strategyPath), ".json") |
| 223 | |
| 224 | return &strategy, nil |
| 225 | } |
| 226 | |
| 227 | // ListStrategies prints available strategies |
| 228 | func (sm *StrategiesManager) ListStrategies(shellCompleteList bool) error { |