Skip to content
Open
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
23 changes: 23 additions & 0 deletions .github/workflows/argvio-metrics.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Argvio metrics

# Validates argvio.metrics.yaml on pull requests and syncs it to Argvio on
# release, so the Product dashboard knows what each metric means.
on:
pull_request:
paths:
- argvio.metrics.yaml
- .github/workflows/argvio-metrics.yml
release:
types: [published]
workflow_dispatch:

permissions:
contents: read

jobs:
argvio-metrics:
uses: cerberauth/ci/.github/workflows/argvio-metrics.yml@main
with:
sync: ${{ github.event_name != 'pull_request' }}
secrets:
ARGVIO_METRICS_ADMIN_KEY: ${{ secrets.ARGVIO_METRICS_ADMIN_KEY }}
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ Full documentation, including installation options, the CLI reference, the
checks reference, and CI/Docker guides, is at
[cerberauth.com/docs/proxyaudit](https://www.cerberauth.com/docs/proxyaudit).

## Telemetry

The CLI collects anonymous usage data to help improve the tool: which command ran, the names (never the values) of the flags used, how the command exited and how long it took, the CLI version, OS and architecture, and basic resource usage, how often a command was mistyped (never what you typed), whether it crashed (never the crash message), plus a few counts that never contain your input: the `--engine` hint given, the outcome of each check (passed, vulnerable, skipped or error, with the check's name and severity) and how many findings each scan reports by severity. Interactive runs also include a random install and session identifier, the shell and terminal emulator you run it in (from a fixed list) and whether its output is piped; runs in CI are fully anonymous. Targets, responses and evidence are never collected. You can opt out of telemetry by passing the `--sqa-opt-out` flag, or by setting `DO_NOT_TRACK=1` (or `ARGVIO_DISABLED=1`).

## Contributing

Issues and pull requests are welcome. Run the test suite with:
Expand Down
55 changes: 55 additions & 0 deletions argvio.metrics.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/getargvio/argvio/main/schema/metrics/v1.schema.json
#
# proxyaudit's product metrics, as shown on the Argvio Product dashboard.
# Synced to Argvio on release (.github/workflows/argvio-metrics.yml); see
# https://github.com/getargvio/argvio/blob/main/docs/metric-catalog.md.
# Every dimension value is a fixed identifier chosen in code.
version: 1
service: proxyaudit
metrics:
- name: scan.engine
kind: counter
title: Scans by proxy engine
description: Scans by the --engine hint given; unknown when none was.
dims: [engine]
dim_order:
engine: [nginx, traefik, envoy, caddy, haproxy, unknown]
# Scan outcome, sent by x/telemetryx/scanmetrics.
- name: scan.checks
kind: counter
title: Check results
description: Every check run in a scan, by outcome. Error means the check could not be evaluated.
dims: [status]
dim_order:
status: [vulnerable, passed, skipped, error]
- name: scan.vulnerable
kind: counter
title: Vulnerable checks
description: Checks that reported at least one finding, by check and by the check's severity (from its CVSS score).
dims: [check, severity]
dim_order:
severity: [critical, high, medium, low, info]
- name: scan.findings
kind: value
title: Findings per scan
description: Findings reported by one scan, by severity, zeros included, so the share of scans that find something is meaningful.
dims: [severity]
dim_order:
severity: [critical, high, medium, low, info]
- name: scan.vulnerable-rate
kind: ratio
title: Share of checks finding something
description: Vulnerable check results over all check results that ran to completion or were skipped.
unit: "%"
scale: 100
numerator: { metric: scan.checks, where: { status: vulnerable } }
denominator: { metric: scan.checks }
- name: scan.error-rate
kind: ratio
title: Share of checks in error
description: Check results that could not be evaluated (network errors, unexpected responses), over all check results.
unit: "%"
scale: 100
direction: down_is_good
numerator: { metric: scan.checks, where: { status: error } }
denominator: { metric: scan.checks }
40 changes: 13 additions & 27 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ import (
"github.com/spf13/cobra"
)

// argvioAPIKey is proxyaudit's public_ingest-scoped Argvio API key, selecting the
// "proxyaudit" tenant its usage analytics land in. It only allows sending usage
// data, so it is safe to embed.
const argvioAPIKey = "argv_live_pub_RNd_Ho771fi4xeSS04er89Xkbs3JOPIp" //nolint:gosec // ingest-only key, not a secret

var (
sqaOptOut bool
otelShutdown func(context.Context) error
toolVersion string
sqaOptOut bool
toolVersion string
)

var name = "proxyaudit"
Expand All @@ -27,17 +31,6 @@ func NewRootCmd(projectVersion, commit, date string) (cmd *cobra.Command) {
Long: `proxyaudit dynamically tests whether a reverse proxy or API gateway is securely configured: TLS/transport, HTTP security headers, client-IP/header trust boundaries, and information disclosure via exposed management interfaces.`,
SilenceErrors: true,
SilenceUsage: true,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if !sqaOptOut {
otelShutdown, _ = telemetryx.New(cmd.Context(), name, projectVersion, telemetryx.WithCommit(commit), telemetryx.WithBuildDate(date))
}
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
if otelShutdown != nil {
_ = otelShutdown(cmd.Context())
otelShutdown = nil
}
},
}

rootCmd.AddCommand(NewScanCmd())
Expand All @@ -51,18 +44,11 @@ func NewRootCmd(projectVersion, commit, date string) (cmd *cobra.Command) {
// This is called by main.main(). It only needs to happen once to the RootCmd.
func Execute(projectVersion, commit, date string) {
c := NewRootCmd(projectVersion, commit, date)
defer func() {
if otelShutdown != nil {
_ = otelShutdown(context.Background())
otelShutdown = nil
}
}()
argvioShutdown := telemetryx.Instrument(c, projectVersion, argvioAPIKey)
defer func() { _ = argvioShutdown(context.Background()) }()

if err := c.Execute(); err != nil {
if otelShutdown != nil {
_ = otelShutdown(context.Background())
otelShutdown = nil
}
if err := telemetryx.Execute(c); err != nil {
_ = argvioShutdown(context.Background())

// errFindingsPresent (scan.go) carries no message — the report
// was already written to every configured sink by the time it's
Expand All @@ -73,8 +59,8 @@ func Execute(projectVersion, commit, date string) {

var runtimeErr *RuntimeError
if errors.As(err, &runtimeErr) {
os.Exit(2) //nolint:gocritic // otelShutdown was already run explicitly above, not relying on the deferred call
os.Exit(2) //nolint:gocritic // argvioShutdown was already run explicitly above, not relying on the deferred call
}
os.Exit(1) //nolint:gocritic // otelShutdown was already run explicitly above, not relying on the deferred call
os.Exit(1) //nolint:gocritic // argvioShutdown was already run explicitly above, not relying on the deferred call
}
}
20 changes: 8 additions & 12 deletions cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,16 @@ import (
"fmt"

"github.com/cerberauth/harnessx"
"github.com/cerberauth/harnessx/reporters"
"github.com/cerberauth/proxyaudit/proxy"
"github.com/cerberauth/proxyaudit/proxy/probebase"
"github.com/cerberauth/proxyaudit/proxy/scanctx"
cobrareportx "github.com/cerberauth/x/cobrax/reportx"
"github.com/cerberauth/x/reportx/harnessreport"
"github.com/cerberauth/x/telemetryx"
"github.com/cerberauth/x/telemetryx/scanmetrics"
"github.com/spf13/cobra"
"go.opentelemetry.io/otel"
)

const scanOtelName = "github.com/cerberauth/proxyaudit/cmd/scan"

var engineNames = map[string]scanctx.ProxyEngine{
"": scanctx.EngineUnknown,
"nginx": scanctx.EngineNginx,
Expand All @@ -26,6 +23,10 @@ var engineNames = map[string]scanctx.ProxyEngine{
"haproxy": scanctx.EngineHAProxy,
}

// engineCounter counts scans by the --engine hint, "unknown" without one
// (argvio.metrics.yaml). Values come from engineNames only.
var engineCounter = telemetryx.NewCounter("scan.engine", "engine")

func NewScanCmd() *cobra.Command {
var (
aggressive bool
Expand Down Expand Up @@ -57,13 +58,6 @@ Use only against systems you own or have explicit written permission to test.`,

ctx := cmd.Context()

otelReporter, _ := reporters.NewOTelReporter(
ctx,
otel.Tracer(scanOtelName),
telemetryx.GetMeterProvider().Meter(scanOtelName),
reporters.WithPrefix("proxyaudit.scan"),
)

sinks, cleanup, err := cobrareportx.SinksFromFlags(cmd)
if err != nil {
return err
Expand All @@ -86,10 +80,12 @@ Use only against systems you own or have explicit written permission to test.`,
ShowAllFindings: showAllFindings,
})

metricsReporter := scanmetrics.New(ctx, scanmetrics.Config{CheckDefs: defs})
hxEngine := harnessx.New(
harnessx.WithChecks(checks...),
harnessx.WithReporters(otelReporter, reportxReporter),
harnessx.WithReporters(reportxReporter, metricsReporter),
)
engineCounter.Add(ctx, 1, string(engine))

scanTarget := scanctx.NewTarget(target, &scanctx.ScanContext{
Engine: engine,
Expand Down
49 changes: 27 additions & 22 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,48 @@ go 1.27.0

require (
github.com/cerberauth/cache-detective v0.0.0-20260917215736-01100a70209b
github.com/cerberauth/harnessx v0.4.0
github.com/cerberauth/x v0.0.0-20260901215913-9d7750841dbb
github.com/cerberauth/harnessx v0.5.0
github.com/cerberauth/x v0.0.0-20261102231323-2cfb133eb4f2
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.12.1
go.opentelemetry.io/otel v1.46.0
)

require (
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cerberauth/reportx v0.3.0 // indirect
github.com/cerberauth/reportx v0.4.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/getargvio/argvio-go v0.5.0 // indirect
github.com/go-logr/logr v1.4.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.33.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.22.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.46.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0 // indirect
go.opentelemetry.io/otel/log v0.22.0 // indirect
go.opentelemetry.io/otel/metric v1.46.0 // indirect
go.opentelemetry.io/otel/sdk v1.46.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.22.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.46.0 // indirect
go.opentelemetry.io/otel/trace v1.46.0 // indirect
go.opentelemetry.io/proto/otlp v1.11.0 // indirect
go.opentelemetry.io/otel v1.47.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.23.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.23.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.47.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.47.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.47.0 // indirect
go.opentelemetry.io/otel/log v1.47.0 // indirect
go.opentelemetry.io/otel/metric v1.47.0 // indirect
go.opentelemetry.io/otel/sdk v1.47.0 // indirect
go.opentelemetry.io/otel/sdk/log v1.47.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.47.0 // indirect
go.opentelemetry.io/otel/trace v1.47.0 // indirect
go.opentelemetry.io/proto/otlp v1.11.1 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/net v0.59.0 // indirect
golang.org/x/sys v0.48.0 // indirect
golang.org/x/text v0.42.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect
google.golang.org/grpc v1.83.2 // indirect
golang.org/x/net v0.62.0 // indirect
golang.org/x/sys v0.49.0 // indirect
golang.org/x/text v0.43.0 // indirect
golang.org/x/time v0.16.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20261026165753-2ffab32bce7e // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20261026165753-2ffab32bce7e // indirect
google.golang.org/grpc v1.85.0 // indirect
google.golang.org/protobuf v1.36.12 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading
Loading