diff --git a/api/query.go b/api/query.go
index 81526ea00a6..34d7f3190ab 100644
--- a/api/query.go
+++ b/api/query.go
@@ -213,6 +213,12 @@ type logWriter struct {
}
func (self *logWriter) Write(b []byte) (int, error) {
+ select {
+ case <-self.ctx.Done():
+ return 0, io.EOF
+ default:
+ }
+
select {
case <-self.ctx.Done():
return 0, io.EOF
diff --git a/artifacts/definitions/Server/Internal/ArtifactDescription.yaml b/artifacts/definitions/Server/Internal/ArtifactDescription.yaml
index a162e616e2f..ca23a16ceef 100644
--- a/artifacts/definitions/Server/Internal/ArtifactDescription.yaml
+++ b/artifacts/definitions/Server/Internal/ArtifactDescription.yaml
@@ -43,7 +43,7 @@ reports:
### Tools
{{ range $artifact.Tools -}}
- *
+ *
{{ end }}
{{ end }}
diff --git a/artifacts/definitions/Server/Utils/CreateCollector.yaml b/artifacts/definitions/Server/Utils/CreateCollector.yaml
index 066e2ae318e..2ec0b347b26 100644
--- a/artifacts/definitions/Server/Utils/CreateCollector.yaml
+++ b/artifacts/definitions/Server/Utils/CreateCollector.yaml
@@ -144,6 +144,16 @@ parameters:
because the packed builtin artifacts are only compatible with
the current release version.
+ - name: opt_delete_at_exit
+ type: bool
+ default: N
+ description: |
+ If specified the collection will be deleted at exit. This only
+ makes sense when uploading to the cloud or a remote
+ location. NOTE: There is no way to check that the upload
+ actually worked so this flag deletes the collection regardless
+ of upload success.
+
- name: StandardCollection
type: hidden
default: |
@@ -267,6 +277,12 @@ parameters:
// The log is always written to the executable path
LET log_filename <= pathspec(parse= baseline[0].Exe + ".log")
+ -- Remove the zip file and log file when done if the user asked for it.
+ LET _ <= if(condition=DeleteOnExit, then=atexit(query={
+ SELECT rm(filename=zip_filename), rm(filename=log_filename) FROM scope()
+ WHERE log(message="Removed Zip file %v", args=zip_filename)
+ }, env=dict(zip_filename=zip_filename, log_filename=log_filename)))
+
-- Make a random hex string as a random password
LET RandomPassword <= SELECT format(format="%02x",
args=rand(range=255)) AS A
@@ -470,6 +486,7 @@ sources:
dict(name="ProgressTimeout", type="int",
default=opt_progress_timeout),
dict(name="Timeout", default=opt_timeout, type="int"),
+ dict(name="DeleteOnExit", default=opt_delete_at_exit, type="bool"),
dict(name="target_args",
default=serialize(format='json', item=target_args),
type="json"),
diff --git a/artifacts/testdata/server/testcases/atexit.out.yaml b/artifacts/testdata/server/testcases/atexit.out.yaml
index 78be35963f7..e5db321578d 100644
--- a/artifacts/testdata/server/testcases/atexit.out.yaml
+++ b/artifacts/testdata/server/testcases/atexit.out.yaml
@@ -12,7 +12,7 @@ SELECT * FROM query(query=''' LET Foo = SELECT log(message="I Ran at the end") F
"Log": "Velociraptor: DEFAULT:Second!\n"
},
{
- "Log": "Velociraptor: Running AtExit query\n"
+ "Log": "Velociraptor: Running AtExit query SELECT log(message=\"I Ran at the end\") FROM scope()\n"
},
{
"Log": "Velociraptor: DEFAULT:I Ran at the end\n"
diff --git a/bin/offline.go b/bin/offline.go
index dbe43b50555..ae061d3f21a 100644
--- a/bin/offline.go
+++ b/bin/offline.go
@@ -90,6 +90,13 @@ OptTimeout: 0
# the packed builtin artifacts are only compatible with the current
# release version.
OptVersion: ""
+
+# If specified the collection will be deleted at exit. This only
+# makes sense when uploading to the cloud or a remote
+# location. NOTE: There is no way to check that the upload
+# actually worked so this flag deletes the collection regardless
+# of upload success.
+OptDeleteAtExit: N
`
func doCollector() error {
@@ -219,7 +226,8 @@ SELECT * FROM Artifact.Server.Utils.CreateCollector(
opt_cpu_limit=Spec.OptCpuLimit,
opt_progress_timeout=Spec.OptProgressTimeout,
opt_timeout=Spec.OptTimeout,
- opt_version=Spec.OptVersion
+ opt_version=Spec.OptVersion,
+ opt_delete_at_exit=Spec.OptDeleteAtExit
)
`
return runQueryWithEnv(query, builder, "json")
diff --git a/docs/offline_collector/offline_collector.sh b/docs/offline_collector/offline_collector.sh
index 7c09d41c45e..6978d4877b8 100755
--- a/docs/offline_collector/offline_collector.sh
+++ b/docs/offline_collector/offline_collector.sh
@@ -68,7 +68,8 @@ SELECT * FROM Artifact.Server.Utils.CreateCollector(
opt_cpu_limit=Spec.OptCpuLimit,
opt_progress_timeout=Spec.OptProgressTimeout,
opt_timeout=Spec.OptTimeout,
- opt_version=Spec.OptVersion
+ opt_version=Spec.OptVersion,
+ opt_delete_at_exit=Spec.OptDeleteAtExit
)
EOF
)
diff --git a/docs/offline_collector/sample.spec.yaml b/docs/offline_collector/sample.spec.yaml
index d2130f68359..b99b7be353e 100644
--- a/docs/offline_collector/sample.spec.yaml
+++ b/docs/offline_collector/sample.spec.yaml
@@ -60,3 +60,10 @@ OptTimeout: 0
# the packed builtin artifacts are only compatible with the current
# release version.
OptVersion: ""
+
+# If specified the collection will be deleted at exit. This only
+# makes sense when uploading to the cloud or a remote
+# location. NOTE: There is no way to check that the upload
+# actually worked so this flag deletes the collection regardless
+# of upload success.
+OptDeleteAtExit: N
diff --git a/gui/velociraptor/package-lock.json b/gui/velociraptor/package-lock.json
index 73c31f1096b..11dda7e6107 100644
--- a/gui/velociraptor/package-lock.json
+++ b/gui/velociraptor/package-lock.json
@@ -10,8 +10,8 @@
"hasInstallScript": true,
"dependencies": {
"@babel/runtime": "^7.26.0",
- "@fortawesome/fontawesome-svg-core": "^6.7.1",
- "@fortawesome/free-regular-svg-icons": "6.6.0",
+ "@fortawesome/fontawesome-svg-core": "6.7.1",
+ "@fortawesome/free-regular-svg-icons": "6.7.1",
"@fortawesome/free-solid-svg-icons": "^6.7.1",
"@fortawesome/react-fontawesome": "0.2.2",
"@popperjs/core": "^2.11.8",
@@ -22,7 +22,7 @@
"classnames": "^2.5.1",
"csv-parse": "4.16.3",
"csv-stringify": "5.6.5",
- "dompurify": "^3.2.1",
+ "dompurify": "3.2.1",
"env-cmd": "^10.1.0",
"hosted-git-info": "^2.8.9",
"html-react-parser": "^0.14.3",
@@ -39,7 +39,7 @@
"react": "^16.14.0",
"react-ace": "^9.5.0",
"react-autosuggest": "^10.1.0",
- "react-bootstrap": "^2.10.6",
+ "react-bootstrap": "2.10.6",
"react-calendar-timeline": "^0.28.0",
"react-contexify": "5.0.0",
"react-dom": "^16.14.0",
@@ -2663,10 +2663,9 @@
}
},
"node_modules/@fortawesome/fontawesome-common-types": {
- "version": "6.6.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
- "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==",
- "license": "MIT",
+ "version": "6.7.1",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz",
+ "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ==",
"engines": {
"node": ">=6"
}
@@ -2683,20 +2682,10 @@
"node": ">=6"
}
},
- "node_modules/@fortawesome/fontawesome-svg-core/node_modules/@fortawesome/fontawesome-common-types": {
- "version": "6.7.1",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz",
- "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/@fortawesome/free-regular-svg-icons": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.1.tgz",
"integrity": "sha512-e13cp+bAx716RZOTQ59DhqikAgETA9u1qTBHO3e3jMQQ+4H/N1NC1ZVeFYt1V0m+Th68BrEL1/X6XplISutbXg==",
- "license": "(CC-BY-4.0 AND MIT)",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.7.1"
},
@@ -2704,15 +2693,6 @@
"node": ">=6"
}
},
- "node_modules/@fortawesome/free-regular-svg-icons/node_modules/@fortawesome/fontawesome-common-types": {
- "version": "6.7.1",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz",
- "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/@fortawesome/free-solid-svg-icons": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.1.tgz",
@@ -2725,15 +2705,6 @@
"node": ">=6"
}
},
- "node_modules/@fortawesome/free-solid-svg-icons/node_modules/@fortawesome/fontawesome-common-types": {
- "version": "6.7.1",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz",
- "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/@fortawesome/react-fontawesome": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz",
@@ -11209,9 +11180,9 @@
"dev": true
},
"@fortawesome/fontawesome-common-types": {
- "version": "6.6.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
- "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw=="
+ "version": "6.7.1",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz",
+ "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ=="
},
"@fortawesome/fontawesome-svg-core": {
"version": "6.7.1",
@@ -11219,13 +11190,6 @@
"integrity": "sha512-8dBIHbfsKlCk2jHQ9PoRBg2Z+4TwyE3vZICSnoDlnsHA6SiMlTwfmW6yX0lHsRmWJugkeb92sA0hZdkXJhuz+g==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.7.1"
- },
- "dependencies": {
- "@fortawesome/fontawesome-common-types": {
- "version": "6.7.1",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz",
- "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ=="
- }
}
},
"@fortawesome/free-regular-svg-icons": {
@@ -11234,13 +11198,6 @@
"integrity": "sha512-e13cp+bAx716RZOTQ59DhqikAgETA9u1qTBHO3e3jMQQ+4H/N1NC1ZVeFYt1V0m+Th68BrEL1/X6XplISutbXg==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.7.1"
- },
- "dependencies": {
- "@fortawesome/fontawesome-common-types": {
- "version": "6.7.1",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz",
- "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ=="
- }
}
},
"@fortawesome/free-solid-svg-icons": {
@@ -11249,13 +11206,6 @@
"integrity": "sha512-BTKc0b0mgjWZ2UDKVgmwaE0qt0cZs6ITcDgjrti5f/ki7aF5zs+N91V6hitGo3TItCFtnKg6cUVGdTmBFICFRg==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.7.1"
- },
- "dependencies": {
- "@fortawesome/fontawesome-common-types": {
- "version": "6.7.1",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz",
- "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ=="
- }
}
},
"@fortawesome/react-fontawesome": {
diff --git a/gui/velociraptor/package.json b/gui/velociraptor/package.json
index fdd4c741ffa..928db30a72c 100644
--- a/gui/velociraptor/package.json
+++ b/gui/velociraptor/package.json
@@ -6,7 +6,7 @@
"dependencies": {
"@babel/runtime": "^7.26.0",
"@fortawesome/fontawesome-svg-core": "6.7.1",
- "@fortawesome/free-regular-svg-icons": "6.6.0",
+ "@fortawesome/free-regular-svg-icons": "6.7.1",
"@fortawesome/free-solid-svg-icons": "^6.7.1",
"@fortawesome/react-fontawesome": "0.2.2",
"@popperjs/core": "^2.11.8",
diff --git a/gui/velociraptor/src/components/flows/offline-collector.jsx b/gui/velociraptor/src/components/flows/offline-collector.jsx
index 48580a6e675..4629aadd7fc 100644
--- a/gui/velociraptor/src/components/flows/offline-collector.jsx
+++ b/gui/velociraptor/src/components/flows/offline-collector.jsx
@@ -563,13 +563,14 @@ class OfflineCollectorParameters extends React.Component {
{T("Pause For Prompt")}
{
+ let value = "N";
if (e.currentTarget.checked) {
- this.props.parameters.opt_prompt = "Y";
- } else {
- this.props.parameters.opt_prompt = "N";
- }
+ value = "Y";
+ };
+ this.props.parameters.opt_prompt = value;
this.props.setParameters(this.props.parameters);
}}
checked={this.props.parameters.opt_prompt === "Y"}
@@ -622,6 +623,24 @@ class OfflineCollectorParameters extends React.Component {
/>
+
+ {T("Delete Collection at Exit")}
+
+ {
+ let value = "N";
+ if (e.currentTarget.checked) {
+ value = "Y";
+ }
+ this.props.parameters.opt_delete_at_exit = value;
+ this.props.setParameters(this.props.parameters);
+ }}
+ checked={this.props.parameters.opt_delete_at_exit === "Y"}
+ />
+
+
@@ -788,6 +807,7 @@ function getDefaultCollectionParameters() {
opt_tempdir: undefined,
opt_filename_template: "Collection-%FQDN%-%TIMESTAMP%",
opt_collector_filename: undefined,
+ opt_delete_at_exit: "N",
opt_format: "jsonl",
opt_prompt: "N",
};
@@ -872,6 +892,9 @@ export default class OfflineCollectorWizard extends React.Component {
case "opt_prompt":
collector_parameters.opt_prompt = value;
break;
+ case "opt_delete_at_exit":
+ collector_parameters.opt_delete_at_exit = value;
+ break;
case "opt_tempdir":
collector_parameters.opt_tempdir = value;
break;
@@ -890,7 +913,6 @@ export default class OfflineCollectorWizard extends React.Component {
case "opt_collector_filename":
collector_parameters.opt_collector_filename = value;
break;
-
case "opt_progress_timeout":
resources.progress_timeout = JSONparse(value);
break;
@@ -958,6 +980,7 @@ export default class OfflineCollectorWizard extends React.Component {
env.push({key: "opt_output_directory", value: str(params.opt_output_directory)});
env.push({key: "opt_filename_template", value: str(params.opt_filename_template)});
env.push({key: "opt_collector_filename", value: str(params.opt_collector_filename)});
+ env.push({key: "opt_delete_at_exit", value: str(params.opt_delete_at_exit)});
env.push({key: "opt_progress_timeout", value: str(this.state.resources.progress_timeout)});
env.push({key: "opt_timeout", value: str(this.state.resources.timeout)});
env.push({key: "opt_cpu_limit", value: str( this.state.resources.cpu_limit)});
diff --git a/vql/tools/atexit.go b/vql/tools/atexit.go
index 05441d4fed1..ac25f907952 100644
--- a/vql/tools/atexit.go
+++ b/vql/tools/atexit.go
@@ -40,20 +40,20 @@ func (self AtExitFunction) Call(
switch t := arg.Query.(type) {
case types.StoredQuery:
+ subscope := scope.Copy()
+ subscope.AppendVars(arg.Env)
+
vql_subsystem.GetRootScope(scope).AddDestructor(func() {
- scope.Log("Running AtExit query")
+ scope.Log("Running AtExit query %v", vfilter.FormatToString(scope, t))
// We need to create a new context to run the
- // desctructors in because the main context
+ // destructors in because the main context
// may already be cancelled.
ctx, cancel := context.WithTimeout(
context.Background(),
time.Duration(timeout)*time.Second)
defer cancel()
- subscope := scope.Copy()
- subscope.AppendVars(arg.Env)
-
for _ = range t.Eval(ctx, subscope) {
}
})
@@ -61,7 +61,7 @@ func (self AtExitFunction) Call(
scope.Log("atexit: Query type %T not supported.", arg.Query)
}
- return vfilter.Null{}
+ return true
}
func (self AtExitFunction) Info(scope vfilter.Scope, type_map *vfilter.TypeMap) *vfilter.FunctionInfo {