summaryrefslogtreecommitdiffstats
path: root/internal/state/state.go
diff options
context:
space:
mode:
authorbndw <ben@bdw.to>2026-02-10 21:29:08 -0800
committerbndw <ben@bdw.to>2026-02-10 21:29:08 -0800
commit47d4b3b6e4d68660e6e1e05fe2e1c0839f86e40e (patch)
treeaf9b3274d2c4ef2bdcdfc1a074c52a52f8d523e3 /internal/state/state.go
parent86a9dbce8b6c067c7e94bc6ba5a078b7d85eb9ca (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.go13
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
43var 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.
47func 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
42func Load() (*State, error) { 55func Load() (*State, error) {
43 path := statePath() 56 path := statePath()