Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
df20681
feat: add verify command JSON report
nullifysecurity Jan 4, 2026
c068283
test: add verify JSON report tests
nullifysecurity Jan 4, 2026
0a3f760
fix: make format case-insensitive
nullifysecurity Jan 4, 2026
c370bb2
fix: create output path if it doesn't exist
nullifysecurity Jan 4, 2026
d5acdc0
Merge branch 'Velocidex:master' into feature/verify-json-report
nullifysecurity Jan 5, 2026
4cf7e1e
revert: json report feature
nullifysecurity Jan 5, 2026
e1d5557
fix: increase read length to 4 MiB
nullifysecurity Jan 5, 2026
73f9ca1
feat: initial refactor of verify command to use VQL function
nullifysecurity Jan 6, 2026
c29e550
feat: add "disable_override" argument to "verify" function
nullifysecurity Jan 6, 2026
3ef69e8
fix: remove local repository to fix built-in logic check
nullifysecurity Jan 6, 2026
2ec6a40
test: add tests for verify "disable_override" argument
nullifysecurity Jan 6, 2026
892e130
feat: re-implement "builtin" flag functionality for verify command
nullifysecurity Jan 6, 2026
8378167
feat: initial implementation of "repository" argument in artifact_set…
nullifysecurity Jan 6, 2026
18412a3
feat: initial implementation of "repository" argument in verify function
nullifysecurity Jan 7, 2026
83d96e1
feat: initial implementation of local repository in verify command
nullifysecurity Jan 7, 2026
50b3346
docs: update vql reference docs
nullifysecurity Jan 7, 2026
9a2ff29
fix: materialize definitions and add filter
nullifysecurity Jan 7, 2026
07b8726
fix: add tag to repository cache key
nullifysecurity Jan 7, 2026
7440fe8
fix: import repository cache key constant
nullifysecurity Jan 7, 2026
0d6a5d5
Some bugfixes
scudette Jan 7, 2026
a9ad8af
fix: add missing "disable_override" argument from query
nullifysecurity Jan 7, 2026
0ab2d18
fix: add check for built-in artifacts in set_artifact
nullifysecurity Jan 7, 2026
17aa307
fix: format query VQL
nullifysecurity Jan 7, 2026
b5b15d8
fix: remove length from read_file
nullifysecurity Jan 7, 2026
5a7021d
feat: add "issues_only" flag to "verify" command
nullifysecurity Jan 7, 2026
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
10 changes: 7 additions & 3 deletions artifacts/definitions/Server/Utils/ArtifactVerifier.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,23 @@ sources:
regex='''^name:\s*(.+)''', string=Artifact).g1

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

LET Artifacts <= SELECT *,
artifact_set(definition=Data, repository="local") AS Definition
FROM Files

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,
FROM foreach(row=Artifacts,
query={
SELECT OSPath AS path,
GetName(Artifact=Data) AS name,
verify(artifact=Data) AS Verify
verify(artifact=Data, repository="local") AS Verify
FROM scope()
})

Expand Down
3 changes: 3 additions & 0 deletions artifacts/testdata/files/artifacts/caller.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: Caller
sources:
- query: SELECT * FROM Artifact.Good()
3 changes: 3 additions & 0 deletions artifacts/testdata/files/artifacts/info.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: Generic.Client.Info
sources:
- query: SELECT * FROM info()
17 changes: 17 additions & 0 deletions artifacts/testdata/server/testcases/verify.in.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ Parameters:
sources:
- query: SELECT * FROM execve(argv=["ls"])

Override1: |
name: Generic.Client.Info
sources:
- query: SELECT * FROM scope()

Override2: |
name: Override2
sources:
- query: SELECT * FROM scope()


Queries:
# Basic verification of some artifacts.
Expand All @@ -35,3 +45,10 @@ Queries:
} AS results
FROM Artifact.Server.Utils.ArtifactVerifier(
SearchGlob=srcDir+"/artifacts/testdata/files/artifacts/*")

# Check the "disable_override" argument produces an error when overriding a built-in,
# but not when creating a new artifact when True is specified.
- SELECT
verify(artifact=Override1, disable_override=True) AS Override1,
verify(artifact=Override2, disable_override=True) AS Override2
FROM scope()
44 changes: 42 additions & 2 deletions artifacts/testdata/server/testcases/verify.out.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,41 @@ Query: SELECT summary, artifacts, { SELECT *, basename(path=path) as path FROM r
Output: [
{
"summary": {
"total": 4,
"passed": 2,
"total": 6,
"passed": 4,
"failed": 2,
"warnings": 1
},
"artifacts": [
"Caller",
"Good",
"Generic.Client.Info",
"BrokenYaml",
"BrokenQuery",
"WarnningExecve"
],
"results": [
{
"name": "Caller",
"passed": true,
"errors": [],
"warnings": [],
"path": "caller.yaml"
},
{
"name": "Good",
"passed": true,
"errors": [],
"warnings": [],
"path": "good.yaml"
},
{
"name": "Generic.Client.Info",
"passed": true,
"errors": [],
"warnings": [],
"path": "info.yaml"
},
{
"name": "BrokenYaml",
"passed": false,
Expand Down Expand Up @@ -99,3 +115,27 @@ Output: [
}
]

# Check the "disable_override" argument produces an error when overriding a built-in,
# but not when creating a new artifact when True is specified.
Query: SELECT verify(artifact=Override1, disable_override=True) AS Override1, verify(artifact=Override2, disable_override=True) AS Override2 FROM scope()
Output: [
{
"Override1": {
"Artifact": "name: Generic.Client.Info\nsources:\n- query: SELECT * FROM scope()\n",
"Permissions": null,
"Errors": [
"Unable to override built in artifact Generic.Client.Info"
],
"Warnings": null,
"Definitions": {}
},
"Override2": {
"Artifact": "name: Override2\nsources:\n- query: SELECT * FROM scope()\n",
"Permissions": null,
"Errors": null,
"Warnings": null,
"Definitions": {}
}
}
]

123 changes: 77 additions & 46 deletions bin/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@ package main

import (
"fmt"
"os"
"log"
"path/filepath"

"github.com/Velocidex/ordereddict"
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"
"www.velocidex.com/golang/velociraptor/services"
"www.velocidex.com/golang/velociraptor/services/launcher"
"www.velocidex.com/golang/velociraptor/startup"
"www.velocidex.com/golang/velociraptor/utils"
"www.velocidex.com/golang/velociraptor/vql/acl_managers"
"www.velocidex.com/golang/vfilter"
)

var (
verify = artifact_command.Command("verify", "Verify a set of artifacts")
verify_args = verify.Arg("paths", "Paths to artifact yaml files").Required().Strings()
verify_allow_override = verify.Flag("builtin", "Allow overriding of built in artifacts").Bool()
verify_issues_only = verify.Flag("issues_only", "If set, we only emit warning and error messages").Bool()
)

func doVerify() error {
Expand Down Expand Up @@ -48,60 +50,89 @@ func doVerify() error {

logger := logging.GetLogger(config_obj, &logging.ToolComponent)

// Report all errors and keep going as much as possible.
artifacts := make(map[string]*artifacts_proto.Artifact)
states := make(map[string]*launcher.AnalysisState)

repository, err := manager.GetGlobalRepository(config_obj)
if err != nil {
return err
}
var artifact_paths []string

for _, artifact_path := range *verify_args {
state := launcher.NewAnalysisState(artifact_path)
states[artifact_path] = state

fd, err := os.Open(artifact_path)
abs, err := filepath.Abs(artifact_path)
if err != nil {
state.SetError(err)
logger.Error("verify: could not get absolute path for %v", artifact_path)
continue
}

data, err := utils.ReadAllWithLimit(fd, constants.MAX_MEMORY)
if err != nil {
state.SetError(err)
continue
}
artifact_paths = append(artifact_paths, abs)
}

a, err := repository.LoadYaml(string(data), services.ArtifactOptions{
ValidateArtifact: true,
ArtifactIsBuiltIn: *verify_allow_override,
AllowOverridingAlias: true,
})
if err != nil {
state.SetError(err)
continue
}
artifacts[artifact_path] = a
artifact_logger := &LogWriter{config_obj: sm.Config}
builder := services.ScopeBuilder{
Config: sm.Config,
ACLManager: acl_managers.NewRoleACLManager(sm.Config, "administrator"),
Logger: log.New(artifact_logger, "", 0),
Env: ordereddict.NewDict().
Set("Artifacts", artifact_paths).
Set("DisableOverride", !*verify_allow_override),
}

for artifact_path, artifact := range artifacts {
state, _ := states[artifact_path]
launcher.VerifyArtifact(ctx, config_obj,
repository, artifact, state)
query := `
-- Load artifacts into local repository
LET Definitions <= SELECT
Filename,
Data,
artifact_set(definition=Data, repository="local") AS Definition
FROM read_file(filenames=Artifacts)

-- Verify artifacts from local repository
SELECT Filename,
Result
FROM foreach(row=Definitions,
query={
SELECT Filename,
verify(artifact=Data,
repository="local",
disable_override=DisableOverride) AS Result
FROM scope()
})
`

scope := manager.BuildScope(builder)
defer scope.Close()

statements, err := vfilter.MultiParse(query)
if err != nil {
logger.Error("verify: error passing query: %v", query)
return err
}

var ret error
for artifact_path, state := range states {
if len(state.Errors) == 0 {
logger.Info("Verified %v: <green>OK</>", artifact_path)
}
for _, err := range state.Errors {
logger.Error("%v: <red>%v</>", artifact_path, err)
ret = errors.New(err)
}
for _, msg := range state.Warnings {
logger.Info("%v: %v", artifact_path, msg)
for _, vql := range statements {
for row := range vql.Eval(sm.Ctx, scope) {
dict := vfilter.RowToDict(ctx, scope, row)

artifact_path, pres := dict.GetString("Filename")
if !pres {
continue
}

result, pres := dict.Get("Result")
if !pres {
continue
}

state, ok := result.(*launcher.AnalysisState)
if !ok {
continue
}
if len(state.Errors) == 0 {
if !*verify_issues_only {
logger.Info("Verified %v: <green>OK</>", artifact_path)
}
}
for _, err := range state.Errors {
logger.Error("%v: <red>%v</>", artifact_path, err)
ret = errors.New(err)
}
for _, msg := range state.Warnings {
logger.Info("%v: %v", artifact_path, msg)
}
}
}

Expand Down
12 changes: 12 additions & 0 deletions docs/references/vql.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@
type: string
description: Optional tags to attach to the artifact.
repeated: true
- name: repository
type: string
description: Add the artifact to this repository, if not set, we add the artifact
to the global repository.
category: server
metadata:
permissions: ARTIFACT_WRITER,SERVER_ARTIFACT_WRITER
Expand Down Expand Up @@ -11588,6 +11592,14 @@
description: The artifact to verify. This can be an artifact source in yaml or
json or the name of an artifact
required: true
- name: repository
type: string
description: The repository to use for verification, if not set, we default to
the global repository.
- name: disable_override
type: bool
description: If set, we do not allow override of built-in artifacts (allowed by
default)
platforms:
- linux_amd64_cgo
- windows_amd64_cgo
Expand Down
22 changes: 14 additions & 8 deletions vql/golang/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import (
"www.velocidex.com/golang/velociraptor/services"
"www.velocidex.com/golang/velociraptor/services/launcher"
vql_subsystem "www.velocidex.com/golang/velociraptor/vql"
vql_server "www.velocidex.com/golang/velociraptor/vql/server"
"www.velocidex.com/golang/vfilter"
"www.velocidex.com/golang/vfilter/arg_parser"
)

type VerifyFunctionArgs struct {
Artifact string `vfilter:"required,field=artifact,doc=The artifact to verify. This can be an artifact source in yaml or json or the name of an artifact"`
Artifact string `vfilter:"required,field=artifact,doc=The artifact to verify. This can be an artifact source in yaml or json or the name of an artifact"`
Repository string `vfilter:"optional,field=repository,doc=The repository to use for verification, if not set, we default to the global repository."`
DisableOverride bool `vfilter:"optional,field=disable_override,doc=If set, we do not allow override of built-in artifacts (allowed by default)"`
}

func init() {
Expand Down Expand Up @@ -55,23 +58,26 @@ This function will verify the artifact and flag any potential errors or warnings

state := launcher.NewAnalysisState(arg.Artifact)

if arg.Repository != "" {
cached_any := vql_subsystem.CacheGet(scope, vql_server.REPOSITORY_CACHE_TAG+arg.Repository)

if cached_repository, ok := cached_any.(services.Repository); ok {
repository = cached_repository
}
}

artifact, pres := repository.Get(ctx, config_obj, arg.Artifact)
if !pres {
local_repository := manager.NewRepository()
local_repository.SetParent(repository, config_obj)

artifact, err = local_repository.LoadYaml(arg.Artifact,
artifact, err = repository.LoadYaml(arg.Artifact,
services.ArtifactOptions{
ValidateArtifact: true,
ArtifactIsBuiltIn: true,
ArtifactIsBuiltIn: !arg.DisableOverride,
AllowOverridingAlias: true,
})
if err != nil {
state.SetError(err)
return state
}

repository = local_repository
}

// Verify the artifact
Expand Down
Loading
Loading