feat(05-01): extend VerifySpec and Finding, add gjson dep

- VerifySpec: add SuccessCodes, FailureCodes, RateLimitCodes, MetadataPaths, Body
- Preserve legacy ValidStatus/InvalidStatus for backward compat
- Add EffectiveSuccessCodes/FailureCodes/RateLimitCodes fallback helpers
- Add ExtractMetadata helper using gjson (skeleton for Plan 05-03)
- Finding: add Verified, VerifyStatus, VerifyHTTPCode, VerifyMetadata, VerifyError
- Add github.com/tidwall/gjson v1.18.0 as direct dependency
This commit is contained in:
salvacybersec
2026-04-05 15:41:13 +03:00
parent 499f5d5025
commit 30c0e9871b
4 changed files with 84 additions and 5 deletions

View File

@@ -3,6 +3,7 @@ package providers
import (
"fmt"
"github.com/tidwall/gjson"
"gopkg.in/yaml.v3"
)
@@ -27,11 +28,73 @@ type Pattern struct {
// VerifySpec defines how to verify a key is live (used by Phase 5 verification engine).
type VerifySpec struct {
Method string `yaml:"method"`
URL string `yaml:"url"`
Headers map[string]string `yaml:"headers"`
ValidStatus []int `yaml:"valid_status"`
InvalidStatus []int `yaml:"invalid_status"`
Method string `yaml:"method"`
URL string `yaml:"url"`
Headers map[string]string `yaml:"headers"`
// Body is an optional request body template; supports {{KEY}} substitution.
Body string `yaml:"body"`
// Canonical status code fields (Phase 5)
SuccessCodes []int `yaml:"success_codes"`
FailureCodes []int `yaml:"failure_codes"`
RateLimitCodes []int `yaml:"rate_limit_codes"`
// MetadataPaths maps display-name -> gjson path (e.g. "org" -> "organization.name").
MetadataPaths map[string]string `yaml:"metadata_paths"`
// Legacy fields kept for backward compat with existing YAMLs (Phase 2-3 providers).
ValidStatus []int `yaml:"valid_status"`
InvalidStatus []int `yaml:"invalid_status"`
}
// EffectiveSuccessCodes returns SuccessCodes if non-empty, else falls back to
// legacy ValidStatus, else the default [200].
func (v VerifySpec) EffectiveSuccessCodes() []int {
if len(v.SuccessCodes) > 0 {
return v.SuccessCodes
}
if len(v.ValidStatus) > 0 {
return v.ValidStatus
}
return []int{200}
}
// EffectiveFailureCodes returns FailureCodes if non-empty, else falls back to
// legacy InvalidStatus, else the default [401, 403].
func (v VerifySpec) EffectiveFailureCodes() []int {
if len(v.FailureCodes) > 0 {
return v.FailureCodes
}
if len(v.InvalidStatus) > 0 {
return v.InvalidStatus
}
return []int{401, 403}
}
// EffectiveRateLimitCodes returns RateLimitCodes if non-empty, else the default [429].
func (v VerifySpec) EffectiveRateLimitCodes() []int {
if len(v.RateLimitCodes) > 0 {
return v.RateLimitCodes
}
return []int{429}
}
// ExtractMetadata applies MetadataPaths (gjson expressions) to a JSON response
// body and returns a display-name -> value map. Paths that do not resolve are
// skipped. Returns nil if no paths are configured or the body is empty.
// Plan 05-03 may extend this with type coercion and nested extraction.
func (v VerifySpec) ExtractMetadata(jsonBody []byte) map[string]string {
if len(v.MetadataPaths) == 0 || len(jsonBody) == 0 {
return nil
}
out := make(map[string]string, len(v.MetadataPaths))
for name, path := range v.MetadataPaths {
result := gjson.GetBytes(jsonBody, path)
if result.Exists() {
out[name] = result.String()
}
}
if len(out) == 0 {
return nil
}
return out
}
// RegistryStats holds aggregate statistics about loaded providers.