-
Notifications
You must be signed in to change notification settings - Fork 4
π‘οΈ Sentinel: [CRITICAL] Fix symlink attack vulnerability in atomic file writes #85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| ## 2026-05-04 - Fix Predictable Temporary File Names for Atomic Writes | ||
| **Vulnerability:** Predictable temporary file names used during atomic file writes (`path + ".tmp"`) are susceptible to symlink attacks, allowing an attacker to overwrite arbitrary files if they have write access to the temporary directory. | ||
| **Learning:** Atomic file writes often rely on temporary files, but using predictable names allows malicious actors to pre-create symlinks pointing to sensitive files. | ||
| **Prevention:** Always use `os.CreateTemp` to generate unpredictable temporary file names, create them in the same directory as the target file to avoid cross-device rename issues, and securely set permissions before writing. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -64,10 +64,29 @@ func LoadOrCreateSnapshotAt(sessionID string, currentCost float64, now time.Time | |||||||||||||||||||||||||||||||||
| return nil, false, err | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| tmpPath := path + ".tmp" | ||||||||||||||||||||||||||||||||||
| if err := os.WriteFile(tmpPath, data, 0644); err != nil { | ||||||||||||||||||||||||||||||||||
| tmpFile, err := os.CreateTemp(filepath.Dir(path), filepath.Base(path)+".*.tmp") | ||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||
| return nil, false, err | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| tmpPath := tmpFile.Name() | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if err := tmpFile.Chmod(0644); err != nil { | ||||||||||||||||||||||||||||||||||
| tmpFile.Close() | ||||||||||||||||||||||||||||||||||
| os.Remove(tmpPath) | ||||||||||||||||||||||||||||||||||
| return nil, false, err | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if _, err := tmpFile.Write(data); err != nil { | ||||||||||||||||||||||||||||||||||
| tmpFile.Close() | ||||||||||||||||||||||||||||||||||
| os.Remove(tmpPath) | ||||||||||||||||||||||||||||||||||
| return nil, false, err | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+79
to
83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To ensure the atomic write is durable,
Suggested change
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if err := tmpFile.Close(); err != nil { | ||||||||||||||||||||||||||||||||||
| os.Remove(tmpPath) | ||||||||||||||||||||||||||||||||||
| return nil, false, err | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if err := os.Rename(tmpPath, path); err != nil { | ||||||||||||||||||||||||||||||||||
| os.Remove(tmpPath) | ||||||||||||||||||||||||||||||||||
| return nil, false, err | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -138,11 +138,33 @@ func Save(sessionID, metric string, b *Buffer) { | |||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| tmp := path + ".tmp" | ||||||||||||||||||||||||||||||||||
| if err := os.WriteFile(tmp, data, 0644); err != nil { | ||||||||||||||||||||||||||||||||||
| tmpFile, err := os.CreateTemp(filepath.Dir(path), filepath.Base(path)+".*.tmp") | ||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| tmpPath := tmpFile.Name() | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if err := tmpFile.Chmod(0644); err != nil { | ||||||||||||||||||||||||||||||||||
| tmpFile.Close() | ||||||||||||||||||||||||||||||||||
| os.Remove(tmpPath) | ||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+147
to
+151
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The temporary file is created in the system's temporary directory. Using |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if _, err := tmpFile.Write(data); err != nil { | ||||||||||||||||||||||||||||||||||
| tmpFile.Close() | ||||||||||||||||||||||||||||||||||
| os.Remove(tmpPath) | ||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+153
to
+157
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To ensure the atomic write is durable,
Suggested change
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if err := tmpFile.Close(); err != nil { | ||||||||||||||||||||||||||||||||||
| os.Remove(tmpPath) | ||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if err := os.Rename(tmpPath, path); err != nil { | ||||||||||||||||||||||||||||||||||
| os.Remove(tmpPath) | ||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| os.Rename(tmp, path) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // PushAndSave is a convenience that loads, pushes, saves, and returns the buffer | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The temporary file is created in the system's temporary directory (
/tmp). Using0644permissions allows any local user to read the snapshot data, which contains session-specific information. Sinceos.CreateTempalready creates the file with secure0600permissions (owner read/write only), it is recommended to remove thisChmodcall unless world-readability is explicitly required for other processes.