Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

knotserver: add secure-mode config and server wiring

author
Anirudh Oppiliappan
date (Jun 12, 2026, 12:02 PM +0300) commit 264d72b3 parent c06ddcf4 change-id ozurvkox
+89 -11
+3
knotserver/config/config.go
··· 27 27 28 28 // This disables signature verification so use with caution. 29 29 Dev bool `env:"DEV, default=false"` 30 + 31 + // SecureMode enables per-repository subprocess isolation. 32 + SecureMode bool `env:"SECURE_MODE, default=false"` 30 33 } 31 34 32 35 type Git struct {
+53 -10
knotserver/server.go
··· 12 12 "tangled.org/core/idresolver" 13 13 "tangled.org/core/jetstream" 14 14 "tangled.org/core/knotserver/config" 15 - "tangled.org/core/knotserver/db" 15 + knotdb "tangled.org/core/knotserver/db" 16 + "tangled.org/core/knotserver/sandbox" 16 17 "tangled.org/core/log" 17 18 "tangled.org/core/notifier" 18 19 "tangled.org/core/rbac" ··· 23 24 Name: "server", 24 25 Usage: "run a knot server", 25 26 Action: Run, 27 + Flags: []cli.Flag{ 28 + &cli.BoolFlag{ 29 + Name: "secure-mode", 30 + Usage: "isolate git subprocesses to their own repository directory", 31 + }, 32 + }, 26 33 Description: ` 27 34 Environment variables: 28 35 KNOT_SERVER_LISTEN_ADDR (default: 0.0.0.0:5555) ··· 33 40 KNOT_SERVER_OWNER (required) 34 41 KNOT_SERVER_LOG_DIDS (default: true) 35 42 KNOT_SERVER_DEV (default: false) 43 + KNOT_SERVER_SECURE_MODE (default: false) 36 44 KNOT_REPO_SCAN_PATH (default: /home/git) 37 45 KNOT_REPO_README (comma-separated list) 38 46 KNOT_REPO_MAIN_BRANCH (default: main) ··· 53 61 return fmt.Errorf("failed to load config: %w", err) 54 62 } 55 63 56 - err = hook.Setup(hook.Config( 57 - hook.WithScanPath(c.Repo.ScanPath), 58 - hook.WithInternalApi(c.Server.InternalListenAddr), 59 - )) 60 - if err != nil { 61 - return fmt.Errorf("failed to setup hooks: %w", err) 64 + // CLI flag overrides env var. 65 + if cmd.Bool("secure-mode") { 66 + c.Server.SecureMode = true 62 67 } 63 - logger.Info("successfully finished setting up hooks") 68 + 69 + if !c.Server.SecureMode { 70 + err = hook.Setup(hook.Config( 71 + hook.WithScanPath(c.Repo.ScanPath), 72 + hook.WithInternalApi(c.Server.InternalListenAddr), 73 + )) 74 + if err != nil { 75 + return fmt.Errorf("failed to setup hooks: %w", err) 76 + } 77 + logger.Info("successfully finished setting up hooks") 78 + } 64 79 65 80 if c.Server.Dev { 66 81 logger.Info("running in dev mode, signature verification is disabled") 67 82 } 68 83 69 - db, err := db.Setup(ctx, c.Server.DBPath) 84 + db, err := knotdb.Setup(ctx, c.Server.DBPath) 70 85 if err != nil { 71 86 return fmt.Errorf("failed to load db: %w", err) 72 87 } ··· 97 112 logger.Warn("knot members backfill failed, continuing", "err", err) 98 113 } 99 114 115 + // probe and initialise the sandbox backend. 116 + var sb sandbox.Backend 117 + if c.Server.SecureMode { 118 + var warn string 119 + sb, warn = sandbox.New(func(repoPath string) (uint32, uint32, error) { 120 + return sandbox.LookupUIDForRepoPath(c.Repo.ScanPath, repoPath) 121 + }) 122 + if warn != "" { 123 + return fmt.Errorf("secure-mode: %s", warn) 124 + } 125 + logger.Info("secure-mode: activated", "backend", sb.Name()) 126 + 127 + // refuse to start if any repos have not yet been isolation-migrated. 128 + unmigrated, countErr := db.CountUnmigratedRepos() 129 + if countErr != nil { 130 + return fmt.Errorf("secure-mode: checking unmigrated repos: %w", countErr) 131 + } 132 + if unmigrated > 0 { 133 + return fmt.Errorf( 134 + "secure-mode: %d repo(s) have not been isolation-migrated; "+ 135 + "run 'knot migrate-isolation' first", 136 + unmigrated, 137 + ) 138 + } 139 + } else { 140 + sb = &sandbox.NoopBackend{} 141 + } 142 + 100 143 go migrateReposOnStartup(ctx, c, db, e, &notifier, log.SubLogger(logger, "migrate")) 101 144 102 - mux, err := Setup(ctx, c, db, e, jc, &notifier, resolver) 145 + mux, err := Setup(ctx, c, db, e, jc, &notifier, resolver, sb) 103 146 if err != nil { 104 147 return fmt.Errorf("failed to setup server: %w", err) 105 148 }
+33 -1
knotserver/xrpc/create_repo.go
··· 204 204 } 205 205 206 206 if data.Source != nil && *data.Source != "" { 207 - err = git.Fork(repoPath, *data.Source, h.Config) 207 + err = git.ForkWithSandbox(repoPath, *data.Source, h.Config, h.Sandbox) 208 208 if err != nil { 209 209 l.Error("forking repo", "error", err.Error()) 210 210 cleanupAll() ··· 259 259 cleanupAll() 260 260 writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 261 261 return 262 + } 263 + 264 + if h.Config.Server.SecureMode { 265 + ownerUID, err := h.Db.GetOrAssignOwnerUID(actorDid.String()) 266 + if err != nil { 267 + l.Error("failed to get/assign owner uid", "error", err.Error()) 268 + cleanupAll() 269 + writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 270 + return 271 + } 272 + if err := sandbox.ChmodRepoTree(repoPath); err != nil { 273 + l.Error("failed to chmod repo tree", "error", err.Error()) 274 + cleanupAll() 275 + writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 276 + return 277 + } 278 + serviceGid, err := sandbox.ServiceGid(h.Config.Repo.ScanPath) 279 + if err != nil { 280 + l.Error("failed to resolve service gid", "error", err.Error()) 281 + cleanupAll() 282 + writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 283 + return 284 + } 285 + if err := sandbox.ChownRepoTree(repoPath, int(ownerUID), int(serviceGid)); err != nil { 286 + l.Error("failed to chown repo tree", "error", err.Error()) 287 + cleanupAll() 288 + writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 289 + return 290 + } 291 + if err := h.Db.MarkRepoIsolated(repoDid); err != nil { 292 + l.Error("failed to mark repo isolated", "error", err.Error()) 293 + } 262 294 } 263 295 264 296 if prepared != nil {