epack install tool validate
Adds to epack.yaml, resolves dependencies, downloads binary.
Run the tool on an evidence pack:
epack tool validate --pack ./evidence.pack
Pass tool-specific arguments after --
Or add manually to epack.yaml:
tools:
validate:
source: https://github.com/locktivity/epack-tool-validate
Then run epack install to lock and sync.
epack-tool-validate is an epack tool that validates evidence packs against compliance profile YAML files. It evaluates whether the artifacts in a pack satisfy the requirements defined in a profile.
The tool performs validation in several stages:
Profiles are YAML files that define compliance requirements:
id: my-compliance-profile
name: My Compliance Profile
version: "1.0.0"
description: A compliance profile for validation
requirements:
- id: REQ-001
name: MFA Required
control: CC6.2
category: Access Control
satisfied_by:
any_of:
- type: evidencepack/idp-posture@v1
metadata_conditions:
all:
- path: $.mfa_enabled
op: eq
value: true
Requirements can use two clause modes:
Conditions use JSONPath expressions to evaluate artifact metadata:
| Operator | Description |
|---|---|
eq |
Equals |
neq |
Not equals |
gt |
Greater than |
gte |
Greater than or equal |
lt |
Less than |
lte |
Less than or equal |
exists |
Value exists (not null) |
not_exists |
Value does not exist (null) |
max_age_days |
ISO date or RFC3339 timestamp is no older than value days (date-only values may be up to one day in the future to absorb timezone differences) |
When using JSONPath expressions that return multiple values (e.g., $.accounts[*].mfa_enabled), you must specify how to evaluate them:
metadata_conditions:
all:
- path: $.accounts[*].mfa_enabled
op: eq
value: true
cardinality: all # ALL values must satisfy the condition
| Cardinality | Description |
|---|---|
| (empty) | Default. Path must return exactly one value |
all |
Every value must satisfy the condition |
any |
At least one value must satisfy the condition |
none |
No value should satisfy the condition |
Clauses can specify a severity level for graded compliance:
satisfied_by:
any_of:
# No severity = full pass
- type: evidencepack/idp-posture@v1
metadata_conditions:
all:
- path: $.mfa_coverage
op: gte
value: 100
# Severity = graded failure
- type: evidencepack/idp-posture@v1
severity: low
metadata_conditions:
all:
- path: $.mfa_coverage
op: gte
value: 90
Clauses can require artifacts to be recent:
- type: evidencepack/vuln-scan@v1
freshness:
max_age_days: 30
metadata_conditions:
all:
- path: $.critical_count
op: eq
value: 0
Use max_age_days when the date to check is inside the artifact body rather than the artifact's manifest timestamp:
- type: evidencepack/pentest-report@v1
metadata_conditions:
all:
- path: $.metadata.test_date
op: max_age_days
value: 365
Evaluation is three-valued. A value that is absent from an artifact (a missing
field, an explicit null, an array element without the field, or an empty
result set) cannot pass or fail a comparison: the condition, its clause, and
the requirement score as unknown with failure_kind:, never as a pass or a graded failure. The presence
"insufficient_evidence"
operators (exists, not_exists) stay two-valued, since absence is exactly
what they test.
Combination follows the evidence: in any_of, a definite pass wins and any
unknown clause otherwise blocks a verdict; in all_of, a definite miss fails
the requirement even when other clauses are unknown. The overall status rolls
up as fail over unknown over pass, and summary.unknown counts the
requirements that could not be scored. Unknown requirements never appear in
key_failures.
The tool outputs a validation.json file containing:
{
"status": "pass",
"profile": {
"id": "my-compliance-profile",
"name": "My Compliance Profile",
"version": "1.0.0",
"digest": "sha256:abc123..."
},
"validated_at": "2024-06-15T12:00:00Z",
"validated_at_label": "just now",
"pack_digest": "sha256:def456...",
"summary": {
"total": 5,
"passed": 5,
"failed": 0,
"missing": 0,
"warnings": 0
},
"requirements": [
{
"id": "REQ-001",
"name": "MFA Required",
"control": "CC6.2",
"category": "Access Control",
"status": "pass",
"artifact": "artifacts/idp-posture.json",
"path": "$.enabled",
"expected": {"op": "eq", "value": true},
"actual": true,
"checks": [
{
"clause_index": 0,
"schema": "evidencepack/idp-posture@v1",
"status": "pass",
"artifact": "artifacts/idp-posture.json",
"conditions": [
{
"path": "$.enabled",
"expected": {"op": "eq", "value": true},
"actual": true,
"passed": true
}
]
}
]
}
],
"by_category": {
"Access Control": {"passed": 3, "failed": 0, "missing": 0},
"System Operations": {"passed": 2, "failed": 0, "missing": 0}
}
}
Overlays allow customizing profiles without modifying the original:
id: my-overlay
name: Production Overlay
# Modify existing requirements
modify:
- id: REQ-001
name: Stricter MFA Requirement
satisfied_by:
any_of:
- type: evidencepack/idp-posture@v1
metadata_conditions:
all:
- path: $.mfa_coverage
op: gte
value: 100
# Skip requirements
skip:
- REQ-003
- REQ-004
# Add new requirements
add:
- id: REQ-NEW
name: New Requirement
satisfied_by:
any_of:
- type: evidencepack/custom@v1
Overlays are applied in order, with last-write-wins semantics.
epack-tool-validate requires a compliance profile to validate against. The profile path is specified in the tool configuration.
| Requirement | Value |
|---|---|
| Requires Pack | Yes |
| Network Access | No |
| Option | Type | Required | Description |
|---|---|---|---|
profile |
string | Yes* | Path to a single profile YAML file |
profiles |
[]string | Yes* | List of profile paths (MVP: only one supported) |
overlays |
[]string | No | List of overlay YAML files to apply |
*Either profile or profiles must be specified. If both are provided, profiles takes precedence.
Add to your build configuration:
# epack.yaml
tools:
validate:
source: locktivity/epack-tool-validate@v1
config:
profile: profiles/security-policy.yaml
Apply environment-specific customizations:
# epack.yaml
tools:
validate:
source: locktivity/epack-tool-validate@v1
config:
profile: profiles/security-policy.yaml
overlays:
- profiles/overlays/production.yaml
- profiles/overlays/us-region.yaml
Profiles are YAML files with the following structure:
id: my-security-profile # Unique identifier
name: My Security Profile # Human-readable name
version: "1.0.0" # Semantic version
description: Example profile # Optional description
requirements:
- id: REQ-001 # Unique requirement ID
name: Access Control Policy # Requirement name
control: CC6.1 # Optional control reference
category: Access Control # Optional category for grouping
satisfied_by:
any_of: # OR: any clause can satisfy
- type: evidencepack/idp-posture@v1
metadata_conditions:
all: # AND: all conditions must match
- path: $.has_access_policy
op: eq
value: true
| Field | Type | Description |
|---|---|---|
type |
string | Required. Artifact schema to match (e.g., evidencepack/idp-posture@v1) |
severity |
string | Optional. Failure severity: critical, high, medium, low |
freshness.max_age_days |
int | Optional. Maximum age of artifact in days |
metadata_conditions.all |
[]Condition | Optional. Conditions that must all be true |
| Field | Type | Description |
|---|---|---|
path |
string | JSONPath expression (e.g., $.mfa_coverage) |
op |
string | Operator: eq, neq, gt, gte, lt, lte, exists, not_exists, max_age_days |
value |
any | Expected value (not required for exists/not_exists) |
cardinality |
string | For multi-value paths: all, any, none (empty = single value required) |
max_age_days expects value to be a non-negative number of days (validated at profile compile time) and the JSONPath value to be an ISO date (YYYY-MM-DD) or RFC3339 timestamp. It passes when the date is no older than the configured number of days and not in the future. Date-only values carry no timezone, so they are allowed up to one day in the future; RFC3339 timestamps must not be in the future at all.
The following JSONPath patterns are supported:
| Pattern | Example | Description |
|---|---|---|
| Root field | $.field |
Access top-level field |
| Nested field | $.nested.field |
Access nested field |
| Array index | $.array[0] |
Access specific array element |
| Last element | $.array[-1] |
Access last array element |
| Wildcard | $.array[*].field |
Access field in all array elements (requires cardinality) |
| Recursive descent | $..field |
Access field at any depth (requires cardinality) |
Note: Multi-value patterns (wildcards, recursive descent) require a cardinality field to specify how multiple results are evaluated. See Cardinality.
When a JSONPath expression can return multiple values, you must specify how to evaluate them:
metadata_conditions:
all:
- path: $.accounts[*].mfa_enabled
op: eq
value: true
cardinality: all # ALL accounts must have MFA enabled
| Cardinality | Description |
|---|---|
| (empty) | Default. Path must return exactly one value |
all |
Every value must satisfy the condition |
any |
At least one value must satisfy the condition |
none |
No value should satisfy the condition |
Examples:
# All accounts must have MFA enabled
- path: $.accounts[*].mfa_enabled
op: eq
value: true
cardinality: all
# At least one environment must be production
- path: $.deployments[*].environment
op: eq
value: production
cardinality: any
# No user should have admin role
- path: $.users[*].role
op: eq
value: admin
cardinality: none
Partial presence detection: For cardinality: all, the validator checks that ALL array elements have the field. If some elements are missing the field entirely, validation fails. For example, $.accounts[*].mfa_enabled with cardinality: all will fail if 2 out of 3 accounts have mfa_enabled: true but the third account has no mfa_enabled field - the result will show "2 of 3 elements have field".
Note: Partial presence detection requires a simple wildcard path (e.g., $.accounts[*].field). Recursive descent paths ($..field) cannot detect partial presence since there's no single base array to count.
Overlays customize profiles without modifying the original:
id: production-overlay # Optional identifier
name: Production Overlay # Optional name
# Modify existing requirements (replaces entire requirement)
modify:
- id: REQ-001
name: Stricter Access Control
satisfied_by:
any_of:
- type: evidencepack/idp-posture@v1
metadata_conditions:
all:
- path: $.has_access_policy
op: eq
value: true
- path: $.policy_reviewed_days
op: lte
value: 90
# Skip requirements entirely
skip:
- REQ-003
- REQ-004
# Add new requirements
add:
- id: REQ-PROD-001
name: Production-Specific Check
category: Production
satisfied_by:
any_of:
- type: evidencepack/deploy-info@v1
metadata_conditions:
all:
- path: $.environment
op: eq
value: production
modify replaces the entire requirement (last-write-wins)skip removes requirements from evaluationadd appends new requirements (ID must be unique)tools:
validate:
source: locktivity/epack-tool-validate@v1
config:
profile: profiles/compliance.yaml
tools:
validate:
source: locktivity/epack-tool-validate@v1
config:
profile: profiles/security-policy.yaml
overlays:
- profiles/overlays/skip-manual.yaml
- profiles/overlays/environment-prod.yaml
Use environment variables in overlay paths:
tools:
validate:
source: locktivity/epack-tool-validate@v1
config:
profile: profiles/security-baseline.yaml
overlays:
- profiles/overlays/${ENVIRONMENT}.yaml
Use YAML anchors to share configuration between multiple tool instances:
tools:
validate:
source: locktivity/epack-tool-validate@v1
config: &base-validate
profile: profiles/security-baseline.yaml
validate-strict:
source: locktivity/epack-tool-validate@v1
config:
<<: *base-validate
overlays:
- profiles/overlays/strict.yaml
| File | Description |
|---|---|
validation.json |
JSON file containing validation results |
| Exit Code | Meaning |
|---|---|
| 0 | Validation completed (check validation.json for pass/fail) |
| 1 | Operational error (profile not found, invalid YAML, etc.) |
Note: A failing validation (requirements not met) still exits with code 0. Check the status field in validation.json: pass, fail, or unknown (evidence absent for at least one requirement, none failing).
Run validation on an evidence pack:
epack tool validate --pack ./my-evidence.epack
{
"status": "pass",
"profile": {
"id": "security-baseline",
"name": "Security Baseline",
"version": "1.0.0"
},
"validated_at": "2024-06-15T12:00:00Z",
"pack_digest": "sha256:abc123...",
"summary": {
"total": 5,
"passed": 5,
"failed": 0,
"warnings": 0
},
"requirements": [
{
"id": "REQ-001",
"name": "MFA Enabled",
"status": "pass",
"artifact": "artifacts/idp-posture.json"
}
]
}
All requirements are satisfied.
{
"status": "fail",
"profile": {
"id": "security-baseline",
"name": "Security Baseline",
"version": "1.0.0"
},
"summary": {
"total": 5,
"passed": 3,
"failed": 1,
"missing": 1,
"warnings": 0
},
"requirements": [
{
"id": "REQ-001",
"name": "MFA Coverage",
"status": "fail",
"severity": "high",
"expected": {
"op": "gte",
"value": 100
},
"actual": 85,
"delta": -15,
"message": "conditions not satisfied",
"artifact": "artifacts/idp-posture.json",
"path": "$.mfa_coverage",
"failure_kind": "condition",
"checks": [
{
"clause_index": 0,
"schema": "evidencepack/idp-posture@v1",
"status": "fail",
"artifact": "artifacts/idp-posture.json",
"message": "conditions not satisfied",
"conditions": [
{
"path": "$.mfa_coverage",
"expected": {"op": "gte", "value": 100},
"actual": 85,
"delta": -15,
"passed": false
}
]
}
]
},
{
"id": "REQ-002",
"name": "Vulnerability Scan",
"status": "fail",
"severity": "high",
"failure_kind": "missing",
"message": "no artifact with schema evidencepack/vuln-scan@v1",
"checks": [
{
"clause_index": 0,
"schema": "evidencepack/vuln-scan@v1",
"status": "missing",
"message": "no artifact with schema evidencepack/vuln-scan@v1"
}
]
}
],
"key_failures": [
{"id": "REQ-001", "name": "MFA Coverage", "severity": "high"},
{"id": "REQ-002", "name": "Vulnerability Scan", "severity": "high"}
]
}
The key_failures array highlights critical and high severity failures.
Output fields for failed requirements:
| Field | Description |
|---|---|
expected |
The condition that needed to be satisfied (op and value) |
actual |
The actual value found in the artifact |
delta |
Numeric difference from expected (for numeric comparisons) |
path |
JSONPath that was evaluated |
artifact |
Path to the artifact that was checked |
failure_kind |
Structured failure classification such as missing or condition |
checks |
Per-clause evaluation details, including missing or passing checks |
id: access-control-basic
name: Basic Access Control Profile
version: "1.0.0"
requirements:
- id: AC-001
name: MFA Enabled
control: AC-1
category: Access Control
satisfied_by:
any_of:
- type: evidencepack/idp-posture@v1
metadata_conditions:
all:
- path: $.mfa_enabled
op: eq
value: true
- id: AC-002
name: Password Policy
control: AC-2
category: Access Control
satisfied_by:
any_of:
- type: evidencepack/idp-posture@v1
metadata_conditions:
all:
- path: $.password_policy.min_length
op: gte
value: 12
- path: $.password_policy.require_special
op: eq
value: true
This profile uses graduated severity for MFA coverage thresholds:
id: mfa-graduated
name: MFA Coverage with Graduated Compliance
version: "1.0.0"
requirements:
- id: MFA-001
name: MFA Coverage
control: CC6.2
category: Access Control
satisfied_by:
any_of:
# Full compliance (100%) - no severity = PASS
- type: evidencepack/idp-posture@v1
metadata_conditions:
all:
- path: $.mfa_coverage
op: gte
value: 100
# 90%+ coverage - low severity
- type: evidencepack/idp-posture@v1
severity: low
metadata_conditions:
all:
- path: $.mfa_coverage
op: gte
value: 90
# 75%+ coverage - medium severity
- type: evidencepack/idp-posture@v1
severity: medium
metadata_conditions:
all:
- path: $.mfa_coverage
op: gte
value: 75
# Anything less - high severity
- type: evidencepack/idp-posture@v1
severity: high
metadata_conditions:
all:
- path: $.mfa_coverage
op: gte
value: 0
| MFA Coverage | Result | Severity |
|---|---|---|
| 100% | Pass | - |
| 95% | Fail | low |
| 80% | Fail | medium |
| 50% | Fail | high |
id: vuln-scan-fresh
name: Vulnerability Scanning Profile
version: "1.0.0"
requirements:
- id: VS-001
name: Recent Vulnerability Scan
control: CC7.1
category: System Operations
satisfied_by:
any_of:
- type: evidencepack/vuln-scan@v1
freshness:
max_age_days: 7
metadata_conditions:
all:
- path: $.critical_count
op: eq
value: 0
- id: VS-002
name: No High Vulnerabilities
control: CC7.2
category: System Operations
satisfied_by:
any_of:
- type: evidencepack/vuln-scan@v1
freshness:
max_age_days: 30
metadata_conditions:
all:
- path: $.high_count
op: lte
value: 5
This requirement needs ALL clauses to be satisfied:
requirements:
- id: DEPLOY-001
name: Production Deployment Checks
category: Deployment
satisfied_by:
all_of:
- type: evidencepack/deploy-info@v1
metadata_conditions:
all:
- path: $.environment
op: eq
value: production
- type: evidencepack/approval@v1
metadata_conditions:
all:
- path: $.approved
op: eq
value: true
- type: evidencepack/test-results@v1
metadata_conditions:
all:
- path: $.all_passed
op: eq
value: true
Use cardinality when checking conditions across arrays:
requirements:
- id: CLOUD-001
name: Root MFA Enabled (All Accounts)
category: Identity
satisfied_by:
any_of:
- type: evidencepack/cloud-posture@v1
metadata_conditions:
all:
# Check ALL accounts have root MFA enabled
- path: $.accounts[*].iam.root_mfa_enabled
op: eq
value: true
cardinality: all
- id: CLOUD-002
name: At Least One High-Availability Region
category: Infrastructure
satisfied_by:
any_of:
- type: evidencepack/cloud-posture@v1
metadata_conditions:
all:
# At least one region has HA enabled
- path: $.regions[*].high_availability
op: eq
value: true
cardinality: any
- id: CLOUD-003
name: No Public S3 Buckets
category: Data Protection
satisfied_by:
any_of:
- type: evidencepack/cloud-posture@v1
metadata_conditions:
all:
# NO bucket should be public
- path: $.s3_buckets[*].public_access
op: eq
value: true
cardinality: none
| Cardinality | Use Case |
|---|---|
all |
Every item must satisfy (e.g., all accounts have MFA) |
any |
At least one item must satisfy (e.g., one region has HA) |
none |
No item should satisfy (e.g., no public buckets) |
id: skip-manual
name: Skip Manual Review Requirements
skip:
- MANUAL-001
- MANUAL-002
- MANUAL-003
id: production-overlay
name: Production Environment Overlay
modify:
- id: VS-001
name: Strict Vulnerability Scan (Production)
satisfied_by:
any_of:
- type: evidencepack/vuln-scan@v1
freshness:
max_age_days: 1 # Daily scans in production
metadata_conditions:
all:
- path: $.critical_count
op: eq
value: 0
- path: $.high_count
op: eq
value: 0
add:
- id: PROD-001
name: Production Monitoring Active
category: Production
satisfied_by:
any_of:
- type: evidencepack/monitoring@v1
metadata_conditions:
all:
- path: $.active
op: eq
value: true
name: Compliance Check
on:
push:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install epack
run: |
go install github.com/locktivity/epack/cmd/epack@latest
- name: Build evidence pack
run: epack collect -o evidence.epack
- name: Validate compliance
run: |
epack tool validate --pack evidence.epack
# Check validation result
STATUS=$(jq -r '.status' validation.json)
if [ "$STATUS" != "pass" ]; then
echo "Compliance check failed"
jq '.key_failures' validation.json
exit 1
fi
echo "Compliance check passed"
compliance:
stage: test
script:
- epack collect -o evidence.epack
- epack tool validate --pack evidence.epack
- |
if [ "$(jq -r '.status' validation.json)" != "pass" ]; then
echo "Compliance failures:"
jq '.requirements[] | select(.status == "fail")' validation.json
exit 1
fi
artifacts:
paths:
- validation.json
when: always
Ensure your artifacts have the expected schema:
# List artifact schemas in pack
epack inspect --pack evidence.epack | jq '.artifacts[].schema'
Test your JSONPath expressions against artifact content:
# Extract artifact and test JSONPath
unzip -p evidence.epack artifacts/idp-posture.json | jq '$.mfa_coverage'
If profile compilation fails, check:
any_of or all_of (not both)type fieldcritical, high, medium, low..) have a cardinality fieldall, any, none**Full Changelog**: https://github.com/locktivity/epack-tool-validate/compare/v0.1.5...v0.1.6
**Full Changelog**: https://github.com/locktivity/epack-tool-validate/compare/v0.1.4...v0.1.5
**Full Changelog**: https://github.com/locktivity/epack-tool-validate/compare/v0.1.3...v0.1.4
**Full Changelog**: https://github.com/locktivity/epack-tool-validate/compare/v0.1.2...v0.1.3
**Full Changelog**: https://github.com/locktivity/epack-tool-validate/compare/v0.1.1...v0.1.2