diff options
| author | bndw <ben@bdw.to> | 2026-02-10 21:29:08 -0800 |
|---|---|---|
| committer | bndw <ben@bdw.to> | 2026-02-10 21:29:08 -0800 |
| commit | 47d4b3b6e4d68660e6e1e05fe2e1c0839f86e40e (patch) | |
| tree | af9b3274d2c4ef2bdcdfc1a074c52a52f8d523e3 /internal/state/state.go | |
| parent | 86a9dbce8b6c067c7e94bc6ba5a078b7d85eb9ca (diff) | |
Harden security: name validation, scoped sudoers, safe.directory
- Add ValidateName() enforcing ^[a-z][a-z0-9-]{0,62}$ on all entry points
- Tighten sudoers to restrict cp sources/destinations and chown targets
- Scope git safe.directory to www-data user only (preserves CVE-2022-24765)
- Add www-data to git group and caddy to www-data group for fcgiwrap
- Fix vanity import template to use orig_uri placeholder
- Restart (not reload) services after group changes
- Add name validation to env subcommands and deploy_cmd
Diffstat (limited to 'internal/state/state.go')
| -rw-r--r-- | internal/state/state.go | 13 |
1 files changed, 13 insertions, 0 deletions
diff --git a/internal/state/state.go b/internal/state/state.go index 324fd34..c9aa21d 100644 --- a/internal/state/state.go +++ b/internal/state/state.go | |||
| @@ -5,6 +5,7 @@ import ( | |||
| 5 | "fmt" | 5 | "fmt" |
| 6 | "os" | 6 | "os" |
| 7 | "path/filepath" | 7 | "path/filepath" |
| 8 | "regexp" | ||
| 8 | ) | 9 | ) |
| 9 | 10 | ||
| 10 | // State represents the entire local deployment state | 11 | // State represents the entire local deployment state |
| @@ -27,6 +28,7 @@ type App struct { | |||
| 27 | Domain string `json:"domain"` | 28 | Domain string `json:"domain"` |
| 28 | Port int `json:"port,omitempty"` // only for type="app" or "git-app" | 29 | Port int `json:"port,omitempty"` // only for type="app" or "git-app" |
| 29 | Repo string `json:"repo,omitempty"` // only for git types, e.g. "/srv/git/foo.git" | 30 | Repo string `json:"repo,omitempty"` // only for git types, e.g. "/srv/git/foo.git" |
| 31 | Public bool `json:"public,omitempty"` // only for git types, enables HTTP clone access | ||
| 30 | Env map[string]string `json:"env,omitempty"` // only for type="app" or "git-app" | 32 | Env map[string]string `json:"env,omitempty"` // only for type="app" or "git-app" |
| 31 | Args string `json:"args,omitempty"` // only for type="app" | 33 | Args string `json:"args,omitempty"` // only for type="app" |
| 32 | Files []string `json:"files,omitempty"` // only for type="app" | 34 | Files []string `json:"files,omitempty"` // only for type="app" |
| @@ -38,6 +40,17 @@ const ( | |||
| 38 | startPort = 8001 | 40 | startPort = 8001 |
| 39 | ) | 41 | ) |
| 40 | 42 | ||
| 43 | var validName = regexp.MustCompile(`^[a-z][a-z0-9-]{0,62}$`) | ||
| 44 | |||
| 45 | // ValidateName checks that a name is safe for use in shell commands, | ||
| 46 | // file paths, systemd units, and DNS labels. | ||
| 47 | func ValidateName(name string) error { | ||
| 48 | if !validName.MatchString(name) { | ||
| 49 | return fmt.Errorf("invalid name %q: must start with a lowercase letter, contain only lowercase letters, digits, and hyphens, and be 1-63 characters", name) | ||
| 50 | } | ||
| 51 | return nil | ||
| 52 | } | ||
| 53 | |||
| 41 | // Load reads state from ~/.config/ship/state.json | 54 | // Load reads state from ~/.config/ship/state.json |
| 42 | func Load() (*State, error) { | 55 | func Load() (*State, error) { |
| 43 | path := statePath() | 56 | path := statePath() |
