Skip to content

Commit 53503f9

Browse files
committed
Add support for --overwrite flag
1 parent 76a82ed commit 53503f9

File tree

6 files changed

+131
-6
lines changed

6 files changed

+131
-6
lines changed

README.md

+28-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
"rm with guard rails"
44

5-
Wraps the rm command with a more secure, safer, and more private version
5+
A wrapper for the "rm" command with soft-deletes, config-based deletion, debug information, and saner defaults
66

7-
## Command line arguments
7+
## Additional command line arguments
88

9+
- `--overwrite` Overwrite the disk location location with zeros
910
- `--hard` Do not soft-delete file
1011
- `--soft` Soft delete a file. A backup will be stored in `/tmp/2rm`
1112
- `--silent` Do not print out additional information priduced by 2rm. This is useful for scripting situations
@@ -42,6 +43,23 @@ By using the `/tmp` directory, the operating system will **automatically hard de
4243
Sometimes you want to hard delete a file/directory every time that you run the `rm` command e.g. you probably want your `node_modules` hard deleted every time and never want to soft delete them.
4344
In this case, you can modify your `~/.local/share/2rm/config.yml` file to always hard delete `node_modules`.
4445

46+
### Overwriting disk location with zeros
47+
48+
When deleting a file with the linux inbuilt `rm` command, the file is still avaliable on disk.
49+
50+
Meaning that the file can still be recovered by any sufficiantly technical user.
51+
52+
This can be problematic when dealing with sensitive files such as private keys that if leaked could lead to catastrophic consequences.
53+
54+
You can overwrite a files disk location (rendering it unrecoverable) by using the `--overwrite` flag.
55+
56+
2rm will still soft-delete the file by default, but the soft-deleted file will be completely filled with zeros.
57+
58+
I made the decision that overwritten files will still be soft deleted because it might be useful for timestamp logging/auditing purposes.
59+
E.g. "when did I overwrite xyz"
60+
61+
If you want to fully delete a file from disk and the file system use both the `--overwrite` and `--hard` flags.
62+
4563
### Config-based deletion
4664

4765
You can specify what directories are soft-deleted anb hard-deleted by using the `~/.local/share/2rm/config.yml` file.
@@ -54,6 +72,14 @@ You can specify what directories are soft-deleted anb hard-deleted by using the
5472
# any files that are soft deleted will be
5573
# backed up in the `backups` directory
5674
backups: /tmp/2rm/
75+
# whenever files matching these paths are deleted
76+
# the disk location will be overwritten with zeros
77+
overwrite:
78+
# when deleting ssh keys, we always want to
79+
# overwrite them with zeros to protect
80+
# against attackers recovering the production
81+
# ssh keys
82+
- ".ssh/*"
5783
hard:
5884
- "node_modules/"
5985
- "target/"

src/config/parse_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ func assertConfig(t *testing.T, configPath string, expectedConfig models.Config)
2525
func TestParsingConfig(t *testing.T) {
2626
expectedConfig := models.Config{
2727
Backups: "/tmp/2rm/",
28+
Overwrite: []string{
29+
".ssh/*",
30+
},
2831
Hard: []string{
2932
"node_modules/",
3033
"target/",

src/models/config.go

+29-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,24 @@ import (
66
)
77

88
type Config struct {
9-
Backups string
10-
Hard []string
11-
Soft []string
9+
// the backup location for soft-deletes
10+
Backups string
11+
12+
// any file paths that match these patterns will be overwritten with
13+
// zeros when deleted
14+
Overwrite []string
15+
16+
// any file paths that match these patterns will be hard-deleted
17+
Hard []string
18+
19+
// any file paths that match these patterns will be soft-deleted
20+
// soft-deletes take precedence over hard-deletes
21+
// meaning that if a file matches both a hard and soft delete pattern
22+
// the file will be soft-deleted
23+
Soft []string
24+
25+
// any file paths that match these patterns will be protected from deletion
26+
// protected files cannot be deleted without the --bypass-protected flag
1227
Protected []string
1328
}
1429

@@ -34,6 +49,17 @@ func (config Config) ShouldSoftDelete(path string) bool {
3449
return false
3550
}
3651

52+
func (config Config) ShouldOverwrite(path string) bool {
53+
for _, overwritePath := range config.Overwrite {
54+
matched := matchesPattern(overwritePath, path)
55+
if matched {
56+
return true
57+
}
58+
}
59+
60+
return false
61+
}
62+
3763
func (config Config) IsProtected(path string) bool {
3864
return util.InArray(config.Protected, path)
3965
}

src/models/config_test.go

+22
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,25 @@ func TestNotProtected(t *testing.T) {
122122
t.Fatalf("Expected %v but got %v", expected, realized)
123123
}
124124
}
125+
126+
func TestShouldOverwrite(t *testing.T) {
127+
testedConfig := loadConfig("valid.yml")
128+
129+
expected := true
130+
realized := testedConfig.ShouldOverwrite(".ssh/test.pem")
131+
132+
if expected != realized {
133+
t.Fatalf("Expected %v but got %v", expected, realized)
134+
}
135+
}
136+
137+
func TestNotShouldOverwrite(t *testing.T) {
138+
testedConfig := loadConfig("valid.yml")
139+
140+
expected := false
141+
realized := testedConfig.ShouldOverwrite("non-existent.txt")
142+
143+
if expected != realized {
144+
t.Fatalf("Expected %v but got %v", expected, realized)
145+
}
146+
}

src/patches/rm.go

+47-1
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ const SOFT_DELETE_CLA = "--soft"
1818
const SILENT_CLA = "--silent"
1919
const DRY_RUN_CLA = "--dry-run"
2020
const BYPASS_PROTECTED_CLA = "--bypass-protected"
21+
const OVERWRITE_CLA = "--overwrite"
2122

2223
func RmPatch(arguments []string, config models.Config) {
2324
forceHardDelete := util.InArray(arguments, HARD_DELETE_CLA)
2425
forceSoftDelete := util.InArray(arguments, SOFT_DELETE_CLA)
2526
silent := util.InArray(arguments, SILENT_CLA)
2627
dryRun := util.InArray(arguments, DRY_RUN_CLA)
2728
bypassProtected := util.InArray(arguments, BYPASS_PROTECTED_CLA)
29+
overwrite := util.InArray(arguments, OVERWRITE_CLA)
2830

2931
actionedArgs := removeUnNeededArguments(
3032
removeDangerousArguments(arguments),
@@ -65,6 +67,18 @@ func RmPatch(arguments []string, config models.Config) {
6567
isConfigHardDelete := config.ShouldHardDelete(absolutePath)
6668
isConfigSoftDelete := config.ShouldSoftDelete(absolutePath)
6769

70+
// overwriting a file is not exclusive to hard/soft deletes
71+
// meaning that you can overwrite the contents of a file with zeros and
72+
// also soft delete it
73+
// I have made this decision because I think soft-deleting an
74+
// overwritten file has auditing/logging use cases
75+
// e.g. Who deleted this file? When was it deleted?
76+
// if we hard deleted the file, we would lose this information
77+
isConfigOverwrite := config.ShouldOverwrite(absolutePath)
78+
if overwrite || isConfigOverwrite {
79+
overwriteFile(absolutePath)
80+
}
81+
6882
if isTmp || forceHardDelete || isConfigHardDelete && !isConfigSoftDelete && !forceSoftDelete {
6983
hardDelete([]string{path}, extractedArguments)
7084
} else {
@@ -91,8 +105,16 @@ func shouldPassthrough(arguments []string) bool {
91105
}
92106

93107
func removeUnNeededArguments(arguments []string) []string {
94-
unNeededArguments := []string{"-r", HARD_DELETE_CLA, SOFT_DELETE_CLA, SILENT_CLA}
95108
returnedArguments := []string{}
109+
unNeededArguments := []string{
110+
"-r",
111+
HARD_DELETE_CLA,
112+
SOFT_DELETE_CLA,
113+
SILENT_CLA,
114+
DRY_RUN_CLA,
115+
BYPASS_PROTECTED_CLA,
116+
OVERWRITE_CLA,
117+
}
96118

97119
for _, arg := range arguments {
98120
if !util.InArray(unNeededArguments, arg) {
@@ -205,3 +227,27 @@ func hardDelete(filePaths []string, arguments []string) {
205227
command := "rm -r " + strings.Join(arguments, " ") + " " + strings.Join(filePaths, " ")
206228
commands.Execute(command)
207229
}
230+
231+
func overwriteFile(filePath string) {
232+
file, err := os.OpenFile(filePath, os.O_RDWR, 0644)
233+
if err != nil {
234+
fmt.Println("Error opening file:", err)
235+
os.Exit(2)
236+
}
237+
defer file.Close()
238+
239+
fileInfo, err := file.Stat()
240+
if err != nil {
241+
fmt.Println("Error getting file info:", err)
242+
os.Exit(2)
243+
}
244+
245+
fileSize := fileInfo.Size()
246+
zeroBytes := make([]byte, fileSize)
247+
248+
_, err = file.WriteAt(zeroBytes, 0)
249+
if err != nil {
250+
fmt.Println("Error writing to file:", err)
251+
os.Exit(2)
252+
}
253+
}

tests/assets/configs/valid.yml

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
backups: /tmp/2rm/
2+
overwrite:
3+
- ".ssh/*"
24
hard:
35
- "node_modules/"
46
- "target/"

0 commit comments

Comments
 (0)