decimate 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +8 -0
- data/README.md +64 -0
- data/Rakefile +1 -0
- data/decimate.gemspec +32 -0
- data/lib/decimate/version.rb +3 -0
- data/lib/decimate.rb +68 -0
- data/spec/lib/decimate_spec.rb +101 -0
- data/spec/spec_helper.rb +21 -0
- metadata +159 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
Copyright (c) 2013 Justin Wiley
|
2
|
+
|
3
|
+
GPL V3, 2007
|
4
|
+
|
5
|
+
http://www.gnu.org/licenses/gpl-3.0.txt
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
8
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# Decimate
|
2
|
+
|
3
|
+
Discipline your file system by securely deleting some of its precious files or directories using shred.
|
4
|
+
|
5
|
+
### Notable features:
|
6
|
+
|
7
|
+
- Uses shred utility to securely delete files, before removing
|
8
|
+
- Allows additional sanity checking of paths
|
9
|
+
- Endeavors to prevent you from accidentally rm -rfing your root dir
|
10
|
+
|
11
|
+
### Usage
|
12
|
+
|
13
|
+
file_path = '/my_app/bad_file.txt'
|
14
|
+
dir_path = '/my_app'
|
15
|
+
|
16
|
+
Decimate.file! file_path # specified file shredded, deleted
|
17
|
+
Decimate.dir! dir_path # all files in all sub-directories shredded, rm -rf the directory
|
18
|
+
|
19
|
+
If the file or dir does not exist, it will return nil without doing anything. Both file! an dir! return the standard out of the executed command (shred) if success. If the shred command fails (or find, which executes shred) and returns any other status code besides 0, an exception will be raised.
|
20
|
+
|
21
|
+
Ruby's File.expand_path is used to check give files or directories, in an attempt to suss out relative paths that might lead to a dangerous delete.
|
22
|
+
|
23
|
+
As an additional sanity check, you can pass a regex pattern using the optional parameter path_must_match:
|
24
|
+
|
25
|
+
Decimate.file file_path, path_must_match: /my_app/
|
26
|
+
|
27
|
+
After the file path is expanded, Decimate will raise if the resulting path does not match the given pattern.
|
28
|
+
|
29
|
+
See RDoc for details.
|
30
|
+
|
31
|
+
### Caveats
|
32
|
+
|
33
|
+
- *Do not feed it raw params from a web-request*. You should carefully white-list anything that comes in.
|
34
|
+
- Since this proxies to the underlying operating system, and returns silently if the file or directory to be deleted no longer exist, I assume this is thread-safe, but no guarantees.
|
35
|
+
- The gem shells out to shred. If shred is not installed, an error will be raised.
|
36
|
+
- Since it's shredding files, disk-recovery utilities won't save you if you accidentally delete something.
|
37
|
+
- Shred has many limitations, especially on journaling file systems, see the man page.
|
38
|
+
- The find command with the -execdir option is used since it's theoretically more secure and may help with race conditions. Read the man page for security implications around $PATH
|
39
|
+
- Decimate has been tested on Ubuntu Linux. It won't work on Windows-based systems.
|
40
|
+
- Decimate has few scruples, it only tries to prevent you from blowing away the root directory, and whatever regex you provide. If you tell it to delete /bin/bash, it will do it.
|
41
|
+
|
42
|
+
Code reviews, comments, violent reactions welcome.
|
43
|
+
|
44
|
+
### Installation
|
45
|
+
|
46
|
+
Add this line to your application's Gemfile:
|
47
|
+
|
48
|
+
gem 'decimate'
|
49
|
+
|
50
|
+
And then execute:
|
51
|
+
|
52
|
+
$ bundle
|
53
|
+
|
54
|
+
Or install it yourself as:
|
55
|
+
|
56
|
+
$ gem install decimate
|
57
|
+
|
58
|
+
## Contributing
|
59
|
+
|
60
|
+
1. Fork it
|
61
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
62
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
63
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
64
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/decimate.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'decimate/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "decimate"
|
8
|
+
spec.version = Decimate::VERSION
|
9
|
+
spec.authors = ["Justin Wiley"]
|
10
|
+
spec.email = ["justin.wiley+decimate@gmail.com"]
|
11
|
+
spec.description = %q{Discipline your file system by securely deleting some of its precious files or directories using shred.}
|
12
|
+
spec.summary = %q{Notable features:
|
13
|
+
|
14
|
+
- Endeavors to prevent you from accidentally rm -rfing your root dir
|
15
|
+
- Uses shred utility to securely delete files, before removing
|
16
|
+
- Allows additional sanity checking of paths}
|
17
|
+
spec.homepage = ""
|
18
|
+
spec.license = "GPLV3"
|
19
|
+
|
20
|
+
spec.files = `git ls-files`.split($/)
|
21
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
22
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.add_dependency "fileutils"
|
26
|
+
spec.add_dependency "open3"
|
27
|
+
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
29
|
+
spec.add_development_dependency "rake"
|
30
|
+
spec.add_development_dependency "pry"
|
31
|
+
spec.add_development_dependency "rspec"
|
32
|
+
end
|
data/lib/decimate.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require "decimate/version"
|
2
|
+
require 'fileutils'
|
3
|
+
require 'open3'
|
4
|
+
|
5
|
+
module Decimate
|
6
|
+
def self.shred_cmd; "shred -uv"; end
|
7
|
+
#
|
8
|
+
# Executes given command using Open3.capture3
|
9
|
+
# Raises exception if non-zero status call returned, writes to error log
|
10
|
+
#
|
11
|
+
def self.run cmd
|
12
|
+
stdout,stderr,status = Open3.capture3 cmd
|
13
|
+
raise "Domination failed: #{cmd}" unless status.nil? || status == 0
|
14
|
+
stdout
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.validate_path path, required_regex=nil
|
18
|
+
raise ArgumentError.new("expected Regexp, given #{required_regex.class}") if required_regex && !required_regex.is_a?(Regexp)
|
19
|
+
File.expand_path(path).tap do |path|
|
20
|
+
raise ArgumentError.new("It looks like you're trying to remove root dir. :( Got #{path}") if path == '/'
|
21
|
+
raise ArgumentError.new("Path #{path} does not match #{required_regex}") if required_regex && !path.match(required_regex)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.fail_unless_shred
|
26
|
+
raise if `which shred`.chomp.empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Securely deletes given file using shred.
|
31
|
+
#
|
32
|
+
# - Returns nil if file does not exist
|
33
|
+
# - Returns stdout from shred operation if file exists and shredded successfully
|
34
|
+
# - If optional regex sanity check is included, exception will be raised if match against given path fails
|
35
|
+
# - Raises if shred or find command triggers any status code other than zero
|
36
|
+
# - Raises if shred command not found
|
37
|
+
#
|
38
|
+
def self.file! path, opts={}
|
39
|
+
return unless File.exist?(path)
|
40
|
+
fail_unless_shred
|
41
|
+
validate_path path, opts[:path_must_match]
|
42
|
+
|
43
|
+
run "#{shred_cmd} #{path}"
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Securely deletes given directory recursively using shred.
|
48
|
+
#
|
49
|
+
# - Returns nil if directory does not exist
|
50
|
+
# - Returns stdout from shred operation if dir exists and shredded successfully
|
51
|
+
# - If optional regex sanity check is included, exception will be raised if match against given path fails
|
52
|
+
# - Raises if shred or find command triggers any status code other than zero
|
53
|
+
# - Raises if shred command not found
|
54
|
+
#
|
55
|
+
# Usage:
|
56
|
+
# Decimate.dir! 'my-unloved-dirctory'
|
57
|
+
# Decimate.dir! 'my-unloved-dirctory', path_must_match: /unloved/
|
58
|
+
#
|
59
|
+
def self.dir! path, opts={}
|
60
|
+
return unless Dir.exist?(path)
|
61
|
+
fail_unless_shred
|
62
|
+
validate_path path, opts[:path_must_match]
|
63
|
+
|
64
|
+
stdout = run "find #{path} -type f -execdir #{shred_cmd} '{}' ';'"
|
65
|
+
FileUtils.rm_rf path
|
66
|
+
stdout
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
describe Decimate do
|
5
|
+
let(:dir) { "tmp" }
|
6
|
+
let(:subdir) { "tmp/subdir" }
|
7
|
+
let(:file) { "#{dir}/file.txt"}
|
8
|
+
let(:file2) { "#{dir}/file2.txt"}
|
9
|
+
let(:file3) { "#{subdir}/file3.txt"}
|
10
|
+
let(:all_files) { [file, file2, file3] }
|
11
|
+
let(:all_content) { [dir, subdir] + all_files }
|
12
|
+
let(:stdout) { 'sample stddout' }
|
13
|
+
|
14
|
+
shared_context 'existing_files_and_dir' do
|
15
|
+
before do
|
16
|
+
FileUtils.mkdir_p subdir
|
17
|
+
all_files.each{|f| FileUtils.touch f}
|
18
|
+
end
|
19
|
+
|
20
|
+
after do
|
21
|
+
FileUtils.rm_rf(dir) if File.exist?(dir)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#validate_path' do
|
26
|
+
it 'should expand and return path' do
|
27
|
+
Decimate.validate_path(file).should == File.expand_path(file)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should raise if expanded path matches /' do
|
31
|
+
expect{Decimate.validate_path('/')}.to raise_error(ArgumentError)
|
32
|
+
expect{Decimate.validate_path('/dir/../')}.to raise_error(ArgumentError)
|
33
|
+
end
|
34
|
+
context 'with required path match argument' do
|
35
|
+
it 'should return path if expanded path matches given' do
|
36
|
+
Decimate.validate_path(file, /tmp/).should == File.expand_path(file)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should raise if not' do
|
40
|
+
expect{Decimate.validate_path(file, /another_dir/)}.to raise_error(ArgumentError)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#fail_unless_shred' do
|
46
|
+
it 'should raise if no shred' do
|
47
|
+
Decimate.should_receive(:`).with("which shred").and_return("")
|
48
|
+
expect{Decimate.fail_unless_shred}.to raise_error
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should not raise if shred' do
|
52
|
+
Decimate.should_receive(:`).with("which shred").and_return("shred")
|
53
|
+
Decimate.fail_unless_shred
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '#file' do
|
58
|
+
include_context 'existing_files_and_dir'
|
59
|
+
|
60
|
+
it 'should check for shred' do
|
61
|
+
Decimate.should_receive(:fail_unless_shred)
|
62
|
+
Decimate.file! file
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should securely delete the given file' do
|
66
|
+
Open3.should_receive(:capture3).with("shred -uv #{file}").and_return([stdout,"",nil])
|
67
|
+
Decimate.file!(file).should == stdout
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should result in file being removed' do
|
71
|
+
File.exist?(file3).should be_true
|
72
|
+
Decimate.file! file3
|
73
|
+
sleep 2 # shred operation takes a while
|
74
|
+
File.exist?(file3).should be_false
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#dir' do
|
79
|
+
include_context 'existing_files_and_dir'
|
80
|
+
|
81
|
+
it 'should check for shred' do
|
82
|
+
Decimate.should_receive(:fail_unless_shred)
|
83
|
+
Decimate.dir! dir
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should securely delete all files under the given file' do
|
87
|
+
Open3.should_receive(:capture3).with("find #{dir} -type f -execdir shred -uv '{}' ';'")
|
88
|
+
Decimate.dir! dir
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should result in all files and dirs being deleted' do
|
92
|
+
all_files.map{|f| File.exist?(f).should be_true}
|
93
|
+
Decimate.dir! dir
|
94
|
+
all_content.each do |f|
|
95
|
+
File.exist?(f).should be_false
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
|
8
|
+
require 'pry'
|
9
|
+
require File.join(File.dirname(__FILE__),'../lib/decimate.rb')
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
13
|
+
config.run_all_when_everything_filtered = true
|
14
|
+
config.filter_run :focus
|
15
|
+
|
16
|
+
# Run specs in random order to surface order dependencies. If you find an
|
17
|
+
# order dependency and want to debug it, you can fix the order by providing
|
18
|
+
# the seed, which is printed after each run.
|
19
|
+
# --seed 1234
|
20
|
+
config.order = 'random'
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: decimate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Justin Wiley
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-07-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: fileutils
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: open3
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bundler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.3'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.3'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: pry
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rspec
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
description: Discipline your file system by securely deleting some of its precious
|
111
|
+
files or directories using shred.
|
112
|
+
email:
|
113
|
+
- justin.wiley+decimate@gmail.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- .gitignore
|
119
|
+
- .rspec
|
120
|
+
- Gemfile
|
121
|
+
- LICENSE.txt
|
122
|
+
- README.md
|
123
|
+
- Rakefile
|
124
|
+
- decimate.gemspec
|
125
|
+
- lib/decimate.rb
|
126
|
+
- lib/decimate/version.rb
|
127
|
+
- spec/lib/decimate_spec.rb
|
128
|
+
- spec/spec_helper.rb
|
129
|
+
homepage: ''
|
130
|
+
licenses:
|
131
|
+
- GPLV3
|
132
|
+
post_install_message:
|
133
|
+
rdoc_options: []
|
134
|
+
require_paths:
|
135
|
+
- lib
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
none: false
|
144
|
+
requirements:
|
145
|
+
- - ! '>='
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
148
|
+
requirements: []
|
149
|
+
rubyforge_project:
|
150
|
+
rubygems_version: 1.8.25
|
151
|
+
signing_key:
|
152
|
+
specification_version: 3
|
153
|
+
summary: ! 'Notable features: - Endeavors to prevent you from accidentally rm -rfing
|
154
|
+
your root dir - Uses shred utility to securely delete files, before removing - Allows
|
155
|
+
additional sanity checking of paths'
|
156
|
+
test_files:
|
157
|
+
- spec/lib/decimate_spec.rb
|
158
|
+
- spec/spec_helper.rb
|
159
|
+
has_rdoc:
|