detached 2436595

fix(security): prevent password exposure in process listings and config files

commit
2436595db33b2be33b8ceec7d31c02e6b3d0c2d5
author
bnema · · 2025-10-26 11:20 UTC
parent
9ef7342
signature
unsigned
4 files · +57 -6
M install/partitioning/format.sh +4 -0
@@ -76,6 +76,10 @@ if [ "$ARCHUP_ENCRYPTION" = "enabled" ]; then
7676
7777 echo "LUKS container opened" >> "$ARCHUP_INSTALL_LOG_FILE"
7878
79+ # Clear password variables from memory
80+ unset LUKS_PASSWORD
81+ unset LUKS_PASSWORD_CONFIRM
82+
7983 # Export the mapped device path
8084 export ARCHUP_CRYPT_ROOT="/dev/mapper/cryptroot"
8185
@@ -76,6 +76,10 @@ if [ "$ARCHUP_ENCRYPTION" = "enabled" ]; then
76 76
77 echo "LUKS container opened" >> "$ARCHUP_INSTALL_LOG_FILE"77 echo "LUKS container opened" >> "$ARCHUP_INSTALL_LOG_FILE"
78 78
79+ # Clear password variables from memory
80+ unset LUKS_PASSWORD
81+ unset LUKS_PASSWORD_CONFIRM
82+
79 # Export the mapped device path83 # Export the mapped device path
80 export ARCHUP_CRYPT_ROOT="/dev/mapper/cryptroot"84 export ARCHUP_CRYPT_ROOT="/dev/mapper/cryptroot"
81 85
M internal/config/config.go +5 -0
@@ -300,6 +300,11 @@ func (c *Config) Save() error {
300300 }
301301 defer file.Close()
302302
303+ // Set restrictive permissions (600) since config contains passwords
304+ if err := os.Chmod(c.ConfigPath, 0600); err != nil {
305+ return fmt.Errorf("failed to set config permissions: %w", err)
306+ }
307+
303308 writer := bufio.NewWriter(file)
304309 defer writer.Flush()
305310
@@ -300,6 +300,11 @@ func (c *Config) Save() error {
300 }300 }
301 defer file.Close()301 defer file.Close()
302 302
303+ // Set restrictive permissions (600) since config contains passwords
304+ if err := os.Chmod(c.ConfigPath, 0600); err != nil {
305+ return fmt.Errorf("failed to set config permissions: %w", err)
306+ }
307+
303 writer := bufio.NewWriter(file)308 writer := bufio.NewWriter(file)
304 defer writer.Flush()309 defer writer.Flush()
305 310
M internal/phases/systemconfig.go +6 -6
@@ -182,9 +182,9 @@ func (p *ConfigPhase) configureNetwork(progressChan chan<- ProgressUpdate) error
182182 func (p *ConfigPhase) createUser(progressChan chan<- ProgressUpdate) error {
183183 p.SendOutput(progressChan, "Creating user account...")
184184
185- // Set root password
186- rootPassCmd := fmt.Sprintf("echo 'root:%s' | chpasswd", p.config.UserPassword)
187- switch err := system.ChrootExec(p.logger.LogPath(),config.PathMnt, rootPassCmd); {
185+ // Set root password using stdin (secure - not visible in process list)
186+ rootPassInput := fmt.Sprintf("root:%s", p.config.UserPassword)
187+ switch err := system.ChrootExecWithStdin(p.logger.LogPath(), config.PathMnt, "chpasswd", rootPassInput); {
188188 case err != nil:
189189 return fmt.Errorf("failed to set root password: %w", err)
190190 }
@@ -196,9 +196,9 @@ func (p *ConfigPhase) createUser(progressChan chan<- ProgressUpdate) error {
196196 return fmt.Errorf("failed to create user: %w", err)
197197 }
198198
199- // Set user password
200- userPassCmd := fmt.Sprintf("echo '%s:%s' | chpasswd", p.config.Username, p.config.UserPassword)
201- switch err := system.ChrootExec(p.logger.LogPath(),config.PathMnt, userPassCmd); {
199+ // Set user password using stdin (secure - not visible in process list)
200+ userPassInput := fmt.Sprintf("%s:%s", p.config.Username, p.config.UserPassword)
201+ switch err := system.ChrootExecWithStdin(p.logger.LogPath(), config.PathMnt, "chpasswd", userPassInput); {
202202 case err != nil:
203203 return fmt.Errorf("failed to set user password: %w", err)
204204 }
@@ -182,9 +182,9 @@ func (p *ConfigPhase) configureNetwork(progressChan chan<- ProgressUpdate) error
182 func (p *ConfigPhase) createUser(progressChan chan<- ProgressUpdate) error {182 func (p *ConfigPhase) createUser(progressChan chan<- ProgressUpdate) error {
183 p.SendOutput(progressChan, "Creating user account...")183 p.SendOutput(progressChan, "Creating user account...")
184 184
185- // Set root password185+ // Set root password using stdin (secure - not visible in process list)
186- rootPassCmd := fmt.Sprintf("echo 'root:%s' | chpasswd", p.config.UserPassword)186+ rootPassInput := fmt.Sprintf("root:%s", p.config.UserPassword)
187- switch err := system.ChrootExec(p.logger.LogPath(),config.PathMnt, rootPassCmd); {187+ switch err := system.ChrootExecWithStdin(p.logger.LogPath(), config.PathMnt, "chpasswd", rootPassInput); {
188 case err != nil:188 case err != nil:
189 return fmt.Errorf("failed to set root password: %w", err)189 return fmt.Errorf("failed to set root password: %w", err)
190 }190 }
@@ -196,9 +196,9 @@ func (p *ConfigPhase) createUser(progressChan chan<- ProgressUpdate) error {
196 return fmt.Errorf("failed to create user: %w", err)196 return fmt.Errorf("failed to create user: %w", err)
197 }197 }
198 198
199- // Set user password199+ // Set user password using stdin (secure - not visible in process list)
200- userPassCmd := fmt.Sprintf("echo '%s:%s' | chpasswd", p.config.Username, p.config.UserPassword)200+ userPassInput := fmt.Sprintf("%s:%s", p.config.Username, p.config.UserPassword)
201- switch err := system.ChrootExec(p.logger.LogPath(),config.PathMnt, userPassCmd); {201+ switch err := system.ChrootExecWithStdin(p.logger.LogPath(), config.PathMnt, "chpasswd", userPassInput); {
202 case err != nil:202 case err != nil:
203 return fmt.Errorf("failed to set user password: %w", err)203 return fmt.Errorf("failed to set user password: %w", err)
204 }204 }
M internal/system/chroot.go +42 -0
@@ -2,6 +2,8 @@ package system
22
33 import (
44 "fmt"
5+ "os"
6+ "os/exec"
57 "strings"
68 )
79
@@ -20,6 +22,46 @@ func ChrootExec(logPath, mountPoint, command string, args ...string) error {
2022 return nil
2123 }
2224
25+// ChrootExecWithStdin executes a command inside chroot with stdin input
26+// This is secure for passing sensitive data like passwords without exposing them in process listings
27+func ChrootExecWithStdin(logPath, mountPoint, command, stdin string) error {
28+ cmd := exec.Command("arch-chroot", mountPoint, "bash", "-c", command)
29+
30+ // Set up stdin pipe
31+ stdinPipe, err := cmd.StdinPipe()
32+ if err != nil {
33+ return fmt.Errorf("failed to create stdin pipe: %w", err)
34+ }
35+
36+ // Set up logging
37+ if logPath != "" {
38+ logFile, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
39+ if err == nil {
40+ defer logFile.Close()
41+ cmd.Stdout = logFile
42+ cmd.Stderr = logFile
43+ }
44+ }
45+
46+ // Start the command
47+ if err := cmd.Start(); err != nil {
48+ return fmt.Errorf("failed to start command: %w", err)
49+ }
50+
51+ // Write to stdin and close pipe
52+ if _, err := stdinPipe.Write([]byte(stdin)); err != nil {
53+ return fmt.Errorf("failed to write to stdin: %w", err)
54+ }
55+ stdinPipe.Close()
56+
57+ // Wait for command to complete
58+ if err := cmd.Wait(); err != nil {
59+ return fmt.Errorf("failed to execute in chroot: %w", err)
60+ }
61+
62+ return nil
63+}
64+
2365 // ChrootPacman runs pacman inside the chroot
2466 func ChrootPacman(logPath, mountPoint, operation string, packages ...string) error {
2567 args := append([]string{operation, "--noconfirm"}, packages...)
@@ -2,6 +2,8 @@ package system
2 2
3 import (3 import (
4 "fmt"4 "fmt"
5+ "os"
6+ "os/exec"
5 "strings"7 "strings"
6 )8 )
7 9
@@ -20,6 +22,46 @@ func ChrootExec(logPath, mountPoint, command string, args ...string) error {
20 return nil22 return nil
21 }23 }
22 24
25+// ChrootExecWithStdin executes a command inside chroot with stdin input
26+// This is secure for passing sensitive data like passwords without exposing them in process listings
27+func ChrootExecWithStdin(logPath, mountPoint, command, stdin string) error {
28+ cmd := exec.Command("arch-chroot", mountPoint, "bash", "-c", command)
29+
30+ // Set up stdin pipe
31+ stdinPipe, err := cmd.StdinPipe()
32+ if err != nil {
33+ return fmt.Errorf("failed to create stdin pipe: %w", err)
34+ }
35+
36+ // Set up logging
37+ if logPath != "" {
38+ logFile, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
39+ if err == nil {
40+ defer logFile.Close()
41+ cmd.Stdout = logFile
42+ cmd.Stderr = logFile
43+ }
44+ }
45+
46+ // Start the command
47+ if err := cmd.Start(); err != nil {
48+ return fmt.Errorf("failed to start command: %w", err)
49+ }
50+
51+ // Write to stdin and close pipe
52+ if _, err := stdinPipe.Write([]byte(stdin)); err != nil {
53+ return fmt.Errorf("failed to write to stdin: %w", err)
54+ }
55+ stdinPipe.Close()
56+
57+ // Wait for command to complete
58+ if err := cmd.Wait(); err != nil {
59+ return fmt.Errorf("failed to execute in chroot: %w", err)
60+ }
61+
62+ return nil
63+}
64+
23 // ChrootPacman runs pacman inside the chroot65 // ChrootPacman runs pacman inside the chroot
24 func ChrootPacman(logPath, mountPoint, operation string, packages ...string) error {66 func ChrootPacman(logPath, mountPoint, operation string, packages ...string) error {
25 args := append([]string{operation, "--noconfirm"}, packages...)67 args := append([]string{operation, "--noconfirm"}, packages...)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 0

    ↑↓ moveenter openesc close