Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 2025-02-14 - Predictable Temporary Filename Symlink Vulnerability
**Vulnerability:** Predictable temporary files were created using hardcoded extensions (e.g. `path + ".tmp"`) in shared directories like `/tmp`. This allows an attacker to pre-create a symlink at the predicted location, tricking the application into overwriting an arbitrary file.
**Learning:** Atomic file writes often involve creating a temporary file and renaming it. If the temporary filename is predictable and located in a shared directory, it is vulnerable to symlink attacks.
**Prevention:** Always use `os.CreateTemp` to generate unpredictable temporary filenames. Explicitly set permissions using `Chmod` if matching the original file permissions is necessary, and ensure `Close()` is called before `Rename()`.
23 changes: 21 additions & 2 deletions internal/burnrate/burnrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
f, err := os.CreateTemp(filepath.Dir(path), "prism-burn-tmp-*")
if err != nil {
return nil, false, err
}
tmpPath := f.Name()

if _, err := f.Write(data); err != nil {
f.Close()
os.Remove(tmpPath)
return nil, false, err
}

if err := f.Chmod(0644); err != nil {
f.Close()
os.Remove(tmpPath)
return nil, false, err
}

if err := f.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
Expand Down
24 changes: 22 additions & 2 deletions internal/sparkline/sparkline.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,30 @@ func Save(sessionID, metric string, b *Buffer) {
if err != nil {
return
}
tmp := path + ".tmp"
if err := os.WriteFile(tmp, data, 0644); err != nil {

f, err := os.CreateTemp(filepath.Dir(path), "prism-spark-tmp-*")
if err != nil {
return
}
tmp := f.Name()

if _, err := f.Write(data); err != nil {
f.Close()
os.Remove(tmp)
return
}

if err := f.Chmod(0644); err != nil {
f.Close()
os.Remove(tmp)
return
}

if err := f.Close(); err != nil {
os.Remove(tmp)
return
}

os.Rename(tmp, path)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error from os.Rename is ignored, which can lead to temporary files being leaked in the filesystem if the rename operation fails (e.g., due to permission issues on the target file). Additionally, the function will silently fail to update the persistent storage while the in-memory cache has already been updated. It is recommended to handle the error and ensure the temporary file is removed on failure.

	if err := os.Rename(tmp, path); err != nil {
		os.Remove(tmp)
	}

}

Expand Down
Loading