Skip to content
Merged
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
2 changes: 1 addition & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -799,7 +799,7 @@ func (self *ApiServer) SetArtifactFile(
}

for _, e := range state.Errors {
res.Errors = append(res.Errors, e.Error())
res.Errors = append(res.Errors, e)
}

return res, nil
Expand Down
2 changes: 1 addition & 1 deletion api/artifacts.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ func checkArtifact(

if err != nil {
return &launcher.AnalysisState{
Errors: []error{err},
Errors: []string{err.Error()},
}, nil
}

Expand Down
70 changes: 70 additions & 0 deletions artifacts/definitions/Server/Utils/ArtifactVerifier.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Server.Utils.ArtifactVerifier
description: |
Verify a set of artifacts and returns results in a structured way.

You can run this on the command line like:
```
velociraptor -r Server.Utils.ArtifactVerifier --SearchGlob '/path/to/*.yaml'
```

type: SERVER

parameters:
- name: SearchGlob
default: /tmp/*.yaml
description: A glob to capture all artifacts to verify

- name: ErrorIsFatal
type: bool
default: N
description: If set, an error is produced if any artifact is failed.

sources:
- query: |
-- Convert the array to a string
LET _Stringify(X) = SELECT str(str=_value) AS value
FROM foreach(row=X)
LET Stringify(X) = _Stringify(X=X).value

LET maybeLog(Path) = if(condition=ErrorIsFatal,
then=log(level="ERROR", dedup= -1, message="%v failed!", args=Path),
else=TRUE)

LET PassLogError(Verify, Path) = NOT Verify.Errors
OR NOT maybeLog(Path=Path)

-- Extract the name of the artifact from the raw data - needed if
-- the yaml can not be parsed at all then we need to fallback to a
-- regex.
LET GetName(Artifact) = parse_string_with_regex(
regex='''^name:\s*(.+)''', string=Artifact).g1

LET Files = SELECT OSPath,
read_file(filename=OSPath, length=10000) AS Data
FROM glob(globs=SearchGlob)

LET Results <= SELECT name,
path,
PassLogError(Verify=Verify, Path=path) AS passed,
Stringify(X=Verify.Errors) AS errors,
Stringify(X=Verify.Warnings) AS warnings
FROM foreach(row=Files,
query={
SELECT OSPath AS path,
GetName(Artifact=Data) AS name,
verify(artifact=Data) AS Verify
FROM scope()
})

-- Add some metadata to the output and present in the same row.
SELECT timestamp(epoch=now()) AS timestamp,
config.version as metadata,
dict(
total=len(list=Results),
passed=len(list=filter(list=Results, condition="x=>x.passed")),
failed=len(list=filter(list=Results, condition="x=>NOT x.passed")),
warnings=len(list=filter(list=Results, condition="x=>x.warnings"))
) AS summary,
{ SELECT name FROM Results } as artifacts,
Results as results
FROM scope()
3 changes: 3 additions & 0 deletions artifacts/testdata/files/artifacts/good.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: Good
sources:
- query: SELECT * FROM info()
2 changes: 2 additions & 0 deletions artifacts/testdata/files/artifacts/invalid1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
name: BrokenYaml
someweirdfield: 1
3 changes: 3 additions & 0 deletions artifacts/testdata/files/artifacts/invalid2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: BrokenQuery
sources:
- query: SELECT * FROM nosuchplugin()
3 changes: 3 additions & 0 deletions artifacts/testdata/files/artifacts/invalid3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: WarnningExecve
sources:
- query: SELECT * FROM execve(argv=["ls"])
40 changes: 36 additions & 4 deletions artifacts/testdata/server/testcases/verify.in.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
Parameters:
Broken1: |
name: BrokenYaml
someweirdfield: 1

Broken2: |
name: BrokenQuery
sources:
- query: SELECT * FROM nosuchplugin()

Broken3: |
name: BrokenSubArtifact
sources:
- query: SELECT * FROM Artifact.No.Such.Artifact()

Warn1: |
name: WarnningExecve
sources:
- query: SELECT * FROM execve(argv=["ls"])


Queries:
- SELECT count() AS Count ,
verify(artifact=name) AS V
FROM artifact_definitions()
WHERE type=~'client' AND built_in AND V.Warnings
# Basic verification of some artifacts.
- SELECT verify(artifact=Broken1) AS Broken1,
verify(artifact=Broken2) AS Broken2,
verify(artifact=Broken3) AS Broken3,
verify(artifact=Warn1) AS Warn1
FROM scope()

# Check the Server.Utils.ArtifactVerifier works on some bad
# artifacts. Filter out the absolute path to make test reproducible
- SELECT summary, artifacts, {
SELECT *, basename(path=path) as path
FROM results
} AS results
FROM Artifact.Server.Utils.ArtifactVerifier(
SearchGlob=srcDir+"/artifacts/testdata/files/artifacts/*")
102 changes: 100 additions & 2 deletions artifacts/testdata/server/testcases/verify.out.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,101 @@
Query: SELECT count() AS Count , verify(artifact=name) AS V FROM artifact_definitions() WHERE type=~'client' AND built_in AND V.Warnings
Output: []
# Basic verification of some artifacts.
Query: SELECT verify(artifact=Broken1) AS Broken1, verify(artifact=Broken2) AS Broken2, verify(artifact=Broken3) AS Broken3, verify(artifact=Warn1) AS Warn1 FROM scope()
Output: [
{
"Broken1": {
"Artifact": "name: BrokenYaml\nsomeweirdfield: 1\n",
"Permissions": null,
"Errors": [
"yaml: unmarshal errors:\n line 2: field someweirdfield not found in type proto.Artifact"
],
"Warnings": null,
"Definitions": {}
},
"Broken2": {
"Artifact": "name: BrokenQuery\nsources:\n- query: SELECT * FROM nosuchplugin()\n",
"Permissions": null,
"Errors": [
"BrokenQuery: query: Unknown plugin nosuchplugin()"
],
"Warnings": null,
"Definitions": {}
},
"Broken3": {
"Artifact": "name: BrokenSubArtifact\nsources:\n- query: SELECT * FROM Artifact.No.Such.Artifact()\n",
"Permissions": null,
"Errors": [
"BrokenSubArtifact: query: Unknown artifact reference No.Such.Artifact"
],
"Warnings": null,
"Definitions": {}
},
"Warn1": {
"Artifact": "name: WarnningExecve\nsources:\n- query: SELECT * FROM execve(argv=[\"ls\"])\n",
"Permissions": [
"EXECVE"
],
"Errors": null,
"Warnings": [
"\u003cyellow\u003eSuggestion\u003c/\u003e: Add EXECVE to artifact's required_permissions or implied_permissions fields"
],
"Definitions": {}
}
}
]

# Check the Server.Utils.ArtifactVerifier works on some bad
# artifacts. Filter out the absolute path to make test reproducible
Query: SELECT summary, artifacts, { SELECT *, basename(path=path) as path FROM results } AS results FROM Artifact.Server.Utils.ArtifactVerifier( SearchGlob=srcDir+"/artifacts/testdata/files/artifacts/*")
Output: [
{
"summary": {
"total": 4,
"passed": 2,
"failed": 2,
"warnings": 1
},
"artifacts": [
"Good",
"BrokenYaml",
"BrokenQuery",
"WarnningExecve"
],
"results": [
{
"name": "Good",
"passed": true,
"errors": [],
"warnings": [],
"path": "good.yaml"
},
{
"name": "BrokenYaml",
"passed": false,
"errors": [
"yaml: unmarshal errors:\n line 2: field someweirdfield not found in type proto.Artifact"
],
"warnings": [],
"path": "invalid1.yaml"
},
{
"name": "BrokenQuery",
"passed": false,
"errors": [
"BrokenQuery: query: Unknown plugin nosuchplugin()"
],
"warnings": [],
"path": "invalid2.yaml"
},
{
"name": "WarnningExecve",
"passed": true,
"errors": [],
"warnings": [
"\u003cyellow\u003eSuggestion\u003c/\u003e: Add EXECVE to artifact's required_permissions or implied_permissions fields"
],
"path": "invalid3.yaml"
}
]
}
]

4 changes: 2 additions & 2 deletions bin/artifacts.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ func doArtifactCollect() error {
scope := manager.BuildScope(services.ScopeBuilder{
Config: config_obj,
ACLManager: acl_managers.NullACLManager{},
Logger: log.New(&LogWriter{config_obj: config_obj}, "", 0),
Logger: log.New(logger, "", 0),
Env: ordereddict.NewDict().
Set("Artifacts", *artifact_command_collect_names).
Set("Output", *artifact_command_collect_output).
Expand Down Expand Up @@ -526,7 +526,7 @@ func maybeAddDefinitionsDirectory(config_obj *config_proto.Config) error {
// Windows) we also support some simpler types here
func parseArtifactType(param_type string, param string) string {
switch param_type {
case "multichoice":
case "multichoice", "json_array":
var res []string
err := json.Unmarshal([]byte(param), &res)
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion bin/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"

errors "github.com/go-errors/errors"
artifacts_proto "www.velocidex.com/golang/velociraptor/artifacts/proto"
"www.velocidex.com/golang/velociraptor/constants"
logging "www.velocidex.com/golang/velociraptor/logging"
Expand Down Expand Up @@ -97,7 +98,7 @@ func doVerify() error {
}
for _, err := range state.Errors {
logger.Error("%v: <red>%v</>", artifact_path, err)
ret = err
ret = errors.New(err)
}
for _, msg := range state.Warnings {
logger.Info("%v: %v", artifact_path, msg)
Expand Down
14 changes: 8 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ require (
github.com/russross/blackfriday/v2 v2.1.0
github.com/sebdah/goldie/v2 v2.8.0
github.com/sergi/go-diff v1.4.0
github.com/sirupsen/logrus v1.8.3
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.11.1
github.com/xor-gate/ar v0.0.0-20170530204233-5c72ae81e2b7 // indirect
github.com/xor-gate/debpkg v1.0.0
go.starlark.net v0.0.0-20230925163745-10651d5192ab
golang.org/x/crypto v0.46.0
golang.org/x/mod v0.30.0
golang.org/x/mod v0.31.0
golang.org/x/net v0.48.0
golang.org/x/sys v0.39.0
golang.org/x/text v0.32.0
Expand Down Expand Up @@ -111,7 +111,7 @@ require (
github.com/Velocidex/grok v0.0.1
github.com/Velocidex/ordereddict v0.0.0-20250821063524-02dc06e46238
github.com/Velocidex/sigma-go v0.0.0-20241113062227-c1c5ea4b5250
github.com/Velocidex/tracee_velociraptor v0.0.0-20251231004915-03828c8ab890
github.com/Velocidex/tracee_velociraptor v0.0.0-20260102153735-470363a4efa4
github.com/Velocidex/yara-x-go v0.0.0-20251010010632-d8eaad9c539c
github.com/VirusTotal/gyp v0.9.1-0.20231202132633-bb35dbf177a6
github.com/alecthomas/kingpin/v2 v2.4.0
Expand Down Expand Up @@ -183,6 +183,7 @@ require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/aquasecurity/tracee/api v0.0.0-20251229080346-032e875eaa90 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.2 // indirect
Expand Down Expand Up @@ -211,7 +212,7 @@ require (
github.com/charmbracelet/x/ansi v0.5.2 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20241209212528-0eec74ecaa6f // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cilium/ebpf v0.20.0 // indirect
github.com/cilium/ebpf v0.20.1-0.20251215101449-df5c3096bd8c // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
Expand All @@ -231,7 +232,7 @@ require (
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/goccy/go-yaml v1.19.1 // indirect
github.com/godzie44/go-uring v0.0.0-20220926161041-69611e8b13d5 // indirect
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f // indirect
github.com/golang/glog v1.2.5 // indirect
Expand Down Expand Up @@ -305,7 +306,8 @@ require (
go.opentelemetry.io/otel/trace v1.39.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
golang.org/x/arch v0.23.0 // indirect
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/term v0.38.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
Expand Down
Loading
Loading