Compare commits

..
8 Commits
Author SHA1 Message Date
Tony West bdea5822e8 Incremented version string 2026-02-26 10:34:50 -05:00
Tony West 7bb228d81c Fixed infinite loop when encountering circular redirects 2026-02-26 10:18:37 -05:00
Tony West 3258ca341f Fixed missing length check issue 2026-02-26 10:12:36 -05:00
Tony West 58d4e91760 Fixed variable shadowing issue 2026-02-26 10:05:56 -05:00
Tony West 1f5a946011 Converted status code logic to switch for readability 2026-02-26 10:02:32 -05:00
Tony West 143fdfda1b Merge pull request #50 from aringo-bf/fix/httpbasic-segfault-and-quiet-flag
Fix HTTPBasic segfault and --quiet flag issues
2026-02-26 09:10:36 -05:00
aringo-bf a33eef47f4 Fix HTTPBasic segfault and --quiet flag issues
This commit fixes three critical bugs that caused segmentation faults:

1. HTTPBasic auth ignores --quiet flag
   - HTTPBasic was the only auth type that did not respect --quiet
   - Added quiet flag check to skip prompts and show warning instead
   - Brings HTTPBasic in line with other auth types

2. Nil pointer dereference in http.NewRequest error handling
   - When http.NewRequest failed with context.Canceled or io.EOF,
     code continued with nil req object
   - Restructured error handling to return early when req is nil

3. Unchecked url.Parse errors causing nil pointer panics
   - Multiple url.Parse calls ignored errors, allowing nil derefs
   - Added proper error checking before accessing parsed URL fields

Fixes segmentation fault when processing OpenAPI specs with HTTPBasic
auth in automated/quiet mode.

Bumps version to 2.3.1
2026-02-25 21:57:34 -06:00
Tony West 4c466112ee Added custom date, email, and URL flags 2026-02-03 17:10:52 -05:00
9 changed files with 304 additions and 241 deletions

No files matched your search

+18 -13
View File
@@ -45,20 +45,25 @@ func CheckSecuritySchemes(spec map[string]interface{}) {
if schemeType := scheme["scheme"]; schemeType != nil {
switch schemeType {
case "basic":
fmt.Println("Basic Authentication is accepted. Supply a username and password? (y/N)")
fmt.Scanln(&autoApplyBasicAuth)
autoApplyBasicAuth = strings.ToLower(autoApplyBasicAuth)
if autoApplyBasicAuth == "y" {
fmt.Printf("Enter a username.")
fmt.Scanln(&basicAuthUser)
fmt.Printf("Enter a password.")
fmt.Scanln(&basicAuthPass)
basicAuth = []byte(basicAuthUser + ":" + basicAuthPass)
basicAuthString = base64.StdEncoding.EncodeToString(basicAuth)
log.Infof("Using %s as the Basic Auth value.", basicAuthString)
Headers = append(Headers, "Authorization: Basic "+basicAuthString)
} else {
if quiet {
autoApplyBasicAuth = "n"
log.Warn("A basic authentication header is accepted. Review the spec and craft a header manually using the -H flag.")
} else {
fmt.Println("Basic Authentication is accepted. Supply a username and password? (y/N)")
fmt.Scanln(&autoApplyBasicAuth)
autoApplyBasicAuth = strings.ToLower(autoApplyBasicAuth)
if autoApplyBasicAuth == "y" {
fmt.Printf("Enter a username.")
fmt.Scanln(&basicAuthUser)
fmt.Printf("Enter a password.")
fmt.Scanln(&basicAuthPass)
basicAuth = []byte(basicAuthUser + ":" + basicAuthPass)
basicAuthString = base64.StdEncoding.EncodeToString(basicAuth)
log.Infof("Using %s as the Basic Auth value.", basicAuthString)
Headers = append(Headers, "Authorization: Basic "+basicAuthString)
} else {
log.Warn("A basic authentication header is accepted. Review the spec and craft a header manually using the -H flag.")
}
}
case "bearer":
log.Warn("A bearer token is accepted. Review the spec and craft a token manually using the -H flag.")
+7
View File
@@ -5,6 +5,7 @@ import (
"io"
"os"
"strings"
"time"
"github.com/spf13/cobra"
@@ -44,6 +45,12 @@ responds in an abnormal way, manual testing should be conducted (prepare manual
}
}
_, err := time.Parse("2006-01-02", customDate)
if err != nil {
fmt.Println("An invalid date was supplied. Please supply a date in '2006-01-02' format.")
os.Exit(1)
}
var bodyBytes []byte
client := CheckAndConfigureProxy()
+3
View File
@@ -166,6 +166,9 @@ func findDefinitionFile(urls []string, client http.Client) (bool, *openapi3.T) {
regexPattern := regexp.MustCompile(`(?s)let\s+(\w+)\s*=\s*({.*?});`)
matches := regexPattern.FindAllStringSubmatch(bodyString, -1)
for _, match := range matches {
if len(match) < 3 {
continue
}
jsonContent := match[2]
checkSpec := UnmarshalSpec([]byte(jsonContent))
if strings.HasPrefix(checkSpec.OpenAPI, "2") || strings.HasPrefix(checkSpec.OpenAPI, "3") {
+1 -1
View File
@@ -29,7 +29,7 @@ This list contains the raw endpoints (parameter values will not be appended or m
log.Infof("Gathering endpoints.\n\n")
if swaggerURL != "" {
bodyBytes, _, _ := MakeRequest(client, "GET", swaggerURL, timeout, nil)
bodyBytes, _, _ = MakeRequest(client, "GET", swaggerURL, timeout, nil)
GenerateRequests(bodyBytes, client)
} else {
specFile, err := os.Open(localFile)
+44 -41
View File
@@ -34,12 +34,14 @@ func writeLog(sc int, target, method, errorMsg, response string) {
}
if outfile != "" {
file, err := os.OpenFile(outfile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0644)
var err error
file, err = os.OpenFile(outfile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
fmt.Println("Output file does not exist or cannot be created")
os.Exit(1)
}
defer file.Close()
tempLogger.SetOutput(file)
}
@@ -54,52 +56,53 @@ func writeLog(sc int, target, method, errorMsg, response string) {
}
if verbose {
if sc != 200 {
if sc == 401 || sc == 403 {
logVerboseUnauth(sc, target, method, errorMsg, response, tempLogger)
} else if sc == 301 || sc == 302 {
logVerboseRedirect(sc, target, method, response, tempLogger)
} else if sc == 0 {
logVerboseBad(sc, target, method, response, tempLogger)
} else if sc == 404 {
logNotFound(sc, target, method, errorMsg, tempLogger)
} else if sc == 1 {
logDangerous(target, method, tempLogger)
} else if sc == 8899 {
logVerboseJSON(specTitle, specDescription)
} else {
logVerboseManual(sc, target, method, errorMsg, response, tempLogger)
}
} else {
switch sc {
case 0:
logVerboseBad(sc, target, method, response, tempLogger)
case 1:
logDangerous(target, method, tempLogger)
case 200:
logVerboseAccessible(sc, target, method, response, tempLogger)
case 301:
logVerboseRedirect(sc, target, method, response, tempLogger)
case 302:
logVerboseRedirect(sc, target, method, response, tempLogger)
case 401:
logVerboseUnauth(sc, target, method, errorMsg, response, tempLogger)
case 403:
logVerboseUnauth(sc, target, method, errorMsg, response, tempLogger)
case 404:
logNotFound(sc, target, method, errorMsg, tempLogger)
case 8899:
logVerboseJSON(specTitle, specDescription)
default:
logVerboseManual(sc, target, method, errorMsg, response, tempLogger)
}
} else {
if sc != 200 {
if sc == 401 || sc == 403 {
logUnauth(sc, target, method, errorMsg, tempLogger)
} else if sc == 301 || sc == 302 {
logRedirect(sc, target, method, tempLogger)
} else if sc == 0 {
logBad(sc, target, method, tempLogger)
} else if sc == 404 {
logNotFound(sc, target, method, errorMsg, tempLogger)
} else if sc == 1 {
logDangerous(target, method, tempLogger)
} else if sc == 8899 {
logJSON(specTitle, specDescription)
} else {
logManual(sc, target, method, errorMsg, tempLogger)
}
} else {
if sc == 8899 {
logJSON(specTitle, specDescription)
} else {
logAccessible(sc, target, method, tempLogger)
}
switch sc {
case 0:
logBad(sc, target, method, tempLogger)
case 1:
logDangerous(target, method, tempLogger)
case 200:
logAccessible(sc, target, method, tempLogger)
case 301:
logRedirect(sc, target, method, tempLogger)
case 302:
logRedirect(sc, target, method, tempLogger)
case 401:
logUnauth(sc, target, method, errorMsg, tempLogger)
case 403:
logUnauth(sc, target, method, errorMsg, tempLogger)
case 404:
logNotFound(sc, target, method, errorMsg, tempLogger)
case 8899:
logJSON(specTitle, specDescription)
default:
logManual(sc, target, method, errorMsg, tempLogger)
}
}
responsePreviewLength = tempResponsePreviewLength
file.Close()
}
func logAccessible(status int, target, method string, logger *log.Logger) {
+7
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"io"
"os"
"time"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -22,6 +23,12 @@ This enables you to test specific API functions for common vulnerabilities or mi
}
}
_, err := time.Parse("2006-01-02", customDate)
if err != nil {
fmt.Println("An invalid date was supplied. Please supply a date in '2006-01-02' format.")
os.Exit(1)
}
client := CheckAndConfigureProxy()
fmt.Printf("\n")
+24 -7
View File
@@ -20,6 +20,7 @@ var (
avoidDangerousRequests string
contentType string
dangerousStrings []string = []string{"add", "block", "build", "buy", "change", "clear", "create", "delete", "deploy", "destroy", "drop", "edit", "emergency", "erase", "execute", "insert", "modify", "order", "overwrite", "pause", "purchase", "rebuild", "remove", "replace", "reset", "restart", "revoke", "run", "sell", "send", "set", "start", "stop", "update", "upload", "write"}
depth int = 0
Headers []string
requestStatus int
riskSurveyed bool = false
@@ -59,7 +60,11 @@ func MakeRequest(client http.Client, method, target string, timeout int64, reqDa
}
// Handling of dangerous keywords
u, _ := url.Parse(target)
u, err := url.Parse(target)
if err != nil || u == nil {
log.Printf("Error parsing URL '%s': %v - skipping request.", target, err)
return nil, "", 0
}
endpoint := u.RawPath + "?" + u.RawQuery
for _, v := range dangerousStrings {
if os.Args[1] == "automate" && strings.Contains(endpoint, v) && !strings.Contains(strings.Join(safeWords, ","), v) {
@@ -87,8 +92,11 @@ func MakeRequest(client http.Client, method, target string, timeout int64, reqDa
defer cancel()
req, err := http.NewRequest(method, target, reqData)
if err != nil && err != context.Canceled && err != io.EOF {
log.Fatal("Error: could not create HTTP request - ", err)
if err != nil {
if err != context.Canceled && err != io.EOF {
log.Fatal("Error: could not create HTTP request - ", err)
}
return nil, "", 0
}
for i := range Headers {
@@ -156,26 +164,35 @@ func MakeRequest(client http.Client, method, target string, timeout int64, reqDa
bodyBytes, _ := io.ReadAll(resp.Body)
bodyString := string(bodyBytes)
if (resp.StatusCode == 301 || resp.StatusCode == 302) && strings.Contains(bodyString, "<html>") {
if (resp.StatusCode == 301 || resp.StatusCode == 302) && strings.Contains(bodyString, "<html>") && depth < 10 {
depth += 1
redirect, _ := resp.Location()
bodyBytes, bodyString, requestStatus = MakeRequest(client, method, redirect.Scheme+"://"+redirect.Host+redirect.Path, timeout, reqData)
return bodyBytes, bodyString, requestStatus
}
requestStatus = resp.StatusCode
depth = 0
return bodyBytes, bodyString, requestStatus
}
func CheckContentType(client http.Client, target string) string {
u, _ := url.Parse(target)
u, err := url.Parse(target)
if err != nil || u == nil {
log.Printf("Error parsing URL '%s': %v - skipping request.", target, err)
return ""
}
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
req, err := http.NewRequest("GET", target, nil)
if err != nil && err != context.Canceled && err != io.EOF {
log.Fatal("Error: could not create HTTP request - ", err)
if err != nil {
if err != context.Canceled && err != io.EOF {
log.Fatal("Error: could not create HTTP request - ", err)
}
return ""
}
// User-Agent handling
+7 -1
View File
@@ -7,6 +7,9 @@ import (
var apiTarget string
var basePath string
var customDate string
var customEmail string
var customURL string
var format string
var insecure bool
var localFile string
@@ -49,7 +52,7 @@ $ sj convert -u https://petstore.swagger.io/v2/swagger.json -o openapi.json`,
log.Error("Command not specified. See the --help flag for usage.")
}
},
Version: "2.2.1",
Version: "2.3.2",
}
func Execute() {
@@ -64,6 +67,9 @@ func init() {
rootCmd.AddCommand(convertCmd)
rootCmd.PersistentFlags().StringVarP(&UserAgent, "agent", "A", "Swagger Jacker (github.com/BishopFox/sj)", "Set the User-Agent string.")
rootCmd.PersistentFlags().StringVarP(&basePath, "base-path", "b", "", "Set the API base path if not defined in the definition file (i.e. /V2/).")
rootCmd.PersistentFlags().StringVarP(&customURL, "custom-url", "c", "https://bishopfox.com", "Set a custom URL to test discovered URL parameters.")
rootCmd.PersistentFlags().StringVarP(&customDate, "custom-date", "d", "1990-01-01", "A custom date to test discovered date parameters.")
rootCmd.PersistentFlags().StringVar(&customEmail, "custom-email", "[email protected]", "A custom email address to test discovered email parameters.")
rootCmd.PersistentFlags().StringVarP(&format, "format", "f", "json", "Declare the format of the definition file (json/yaml/yml/js).")
rootCmd.PersistentFlags().StringArrayVarP(&Headers, "headers", "H", nil, "Add custom headers, separated by a colon (\"Name: Value\"). Multiple flags are accepted.")
rootCmd.PersistentFlags().BoolVarP(&insecure, "insecure", "i", false, "Ignores server certificate validation.")
+193 -178
View File
@@ -31,183 +31,6 @@ type SchemaNode struct {
Ref string
}
func EnforceSingleContentType(newContentType string) {
newContentType = strings.TrimSpace(newContentType)
// Remove old 'Content-Type' header
Headers = slices.DeleteFunc(Headers, func(h string) bool {
return strings.HasPrefix(strings.ToLower(h), "content-type:")
})
Headers = append(Headers, "Content-Type: "+newContentType)
// Remove empty elements to avoid repetitions of "-H ''"
Headers = slices.DeleteFunc(Headers, func(h string) bool {
return strings.TrimSpace(h) == ""
})
}
func ExpandSchema(
spec map[string]interface{},
schema map[string]interface{},
visited map[string]bool,
) *SchemaNode {
if schema == nil {
return &SchemaNode{Type: "object"}
}
if ref, ok := schema["$ref"].(string); ok {
if visited[ref] {
return &SchemaNode{Type: "object"} // break cycle
}
visited[ref] = true
resolved := ResolveRef(spec, ref)
if resolved == nil {
return &SchemaNode{Type: "object"}
}
return ExpandSchema(spec, resolved, visited)
}
node := &SchemaNode{
Properties: map[string]*SchemaNode{},
Required: map[string]bool{},
}
if t, ok := schema["type"].(string); ok {
node.Type = t
}
if props, ok := schema["properties"].(map[string]interface{}); ok {
for name, raw := range props {
if m, ok := raw.(map[string]interface{}); ok {
node.Properties[name] = ExpandSchema(spec, m, visited)
}
}
}
if items, ok := schema["items"].(map[string]interface{}); ok {
node.Items = ExpandSchema(spec, items, visited)
}
if allOf, ok := schema["allOf"].([]interface{}); ok {
merged := &SchemaNode{Type: "object", Properties: map[string]*SchemaNode{}}
for _, entry := range allOf {
if m, ok := entry.(map[string]interface{}); ok {
sub := ExpandSchema(spec, m, visited)
for k, v := range sub.Properties {
merged.Properties[k] = v
}
}
}
return merged
}
return node
}
func GenerateExample(node *SchemaNode) interface{} {
switch node.Type {
case "object", "":
obj := map[string]interface{}{}
for k, v := range node.Properties {
obj[k] = GenerateExample(v)
}
return obj
case "array":
if node.Items != nil {
return []interface{}{GenerateExample(node.Items)}
}
return []interface{}{}
case "string":
return testString
case "integer", "number":
return 1
case "boolean":
return true
default:
return nil
}
}
func GenerateRequests(bodyBytes []byte, client http.Client) {
// Ingests the specification file
spec := SafelyUnmarshalSpec(bodyBytes)
// Checks defined security schemes and prompts for authentication
CheckSecuritySchemes(spec)
u, _ := url.Parse(swaggerURL)
// Gets the target server and base path from the specification file
if apiTarget == "" {
if v, ok := spec["swagger"].(string); ok && strings.HasPrefix(v, "2") {
// Swagger (v2)
host, _ := spec["host"].(string)
bp, _ := spec["basePath"].(string)
if bp == "/" {
basePath = ""
} else if bp != "" {
basePath = bp
}
if host != "" && strings.Contains(host, "://") {
apiTarget = host
} else {
if host != "" {
apiTarget = u.Scheme + "://" + host
}
}
} else if v, ok := spec["openapi"].(string); ok && strings.HasPrefix(v, "3") {
// OpenAPI (v3)
if servers, ok := spec["servers"].([]interface{}); ok && len(servers) > 0 {
if len(servers) > 1 {
if !quiet && (os.Args[1] != "endpoints") && apiTarget == "" {
log.Warn("Multiple servers detected in documentation. You can manually set a server to test with the -T flag.\nThe detected servers are as follows:")
for i := range servers {
if srv, ok := servers[i].(map[string]interface{}); ok {
if serverURL, ok := srv["url"].(string); ok {
if strings.Contains(serverURL, "://") {
fmt.Println(serverURL)
} else {
fmt.Println(apiTarget + serverURL)
}
}
}
}
}
} else {
if srv, ok := servers[0].(map[string]interface{}); ok {
if serverURL, ok := srv["url"].(string); ok {
if strings.Contains(serverURL, "://") {
apiTarget = serverURL
} else if serverURL == "/" {
basePath = ""
} else {
basePath = serverURL
apiTarget = u.Scheme + "://" + u.Host
}
}
}
}
}
}
}
// Use the original host at the target if no server found from specification.
if apiTarget == "" {
apiTarget = u.Scheme + "://" + u.Host
}
if os.Args[1] != "endpoints" {
// Prints Title/Description/Version values if they exist
PrintSpecInfo(spec)
}
// Reviews all defined API routes and builds requests as defined
BuildRequestsFromPaths(spec, client)
}
func BuildRequestsFromPaths(spec map[string]interface{}, client http.Client) {
paths, ok := spec["paths"].(map[string]interface{})
if !ok || paths == nil {
@@ -375,7 +198,11 @@ func BuildRequestsFromPaths(spec map[string]interface{}, client http.Client) {
}
}
logURL, _ := url.Parse(targetURL)
logURL, parseErr := url.Parse(targetURL)
if parseErr != nil || logURL == nil {
log.Printf("Error parsing URL '%s': %v - skipping endpoint.", targetURL, parseErr)
continue
}
switch os.Args[1] {
case "automate":
var postBodyData string
@@ -466,6 +293,194 @@ func BuildRequestsFromPaths(spec map[string]interface{}, client http.Client) {
}
}
func EnforceSingleContentType(newContentType string) {
newContentType = strings.TrimSpace(newContentType)
// Remove old 'Content-Type' header
Headers = slices.DeleteFunc(Headers, func(h string) bool {
return strings.HasPrefix(strings.ToLower(h), "content-type:")
})
Headers = append(Headers, "Content-Type: "+newContentType)
// Remove empty elements to avoid repetitions of "-H ''"
Headers = slices.DeleteFunc(Headers, func(h string) bool {
return strings.TrimSpace(h) == ""
})
}
func ExpandSchema(
spec map[string]interface{},
schema map[string]interface{},
visited map[string]bool,
) *SchemaNode {
if schema == nil {
return &SchemaNode{Type: "object"}
}
if ref, ok := schema["$ref"].(string); ok {
if visited[ref] {
return &SchemaNode{Type: "object"} // break cycle
}
visited[ref] = true
resolved := ResolveRef(spec, ref)
if resolved == nil {
return &SchemaNode{Type: "object"}
}
return ExpandSchema(spec, resolved, visited)
}
node := &SchemaNode{
Properties: map[string]*SchemaNode{},
Required: map[string]bool{},
}
if t, ok := schema["type"].(string); ok {
node.Type = t
}
if props, ok := schema["properties"].(map[string]interface{}); ok {
for name, raw := range props {
if m, ok := raw.(map[string]interface{}); ok {
node.Properties[name] = ExpandSchema(spec, m, visited)
}
}
}
if items, ok := schema["items"].(map[string]interface{}); ok {
node.Items = ExpandSchema(spec, items, visited)
}
if allOf, ok := schema["allOf"].([]interface{}); ok {
merged := &SchemaNode{Type: "object", Properties: map[string]*SchemaNode{}}
for _, entry := range allOf {
if m, ok := entry.(map[string]interface{}); ok {
sub := ExpandSchema(spec, m, visited)
for k, v := range sub.Properties {
merged.Properties[k] = v
}
}
}
return merged
}
return node
}
func GenerateExample(node *SchemaNode) interface{} {
switch node.Type {
case "object", "":
obj := map[string]interface{}{}
for k, v := range node.Properties {
if strings.Contains(strings.ToLower(k), "date") {
obj[k] = customDate
} else if strings.Contains(strings.ToLower(k), "url") {
obj[k] = customURL
} else if strings.Contains(strings.ToLower(k), "email") {
obj[k] = customEmail
} else {
obj[k] = GenerateExample(v)
}
}
return obj
case "array":
if node.Items != nil {
return []interface{}{GenerateExample(node.Items)}
}
return []interface{}{}
case "string":
return testString
case "integer", "number":
return 1
case "boolean":
return true
default:
return nil
}
}
func GenerateRequests(bodyBytes []byte, client http.Client) {
// Ingests the specification file
spec := SafelyUnmarshalSpec(bodyBytes)
// Checks defined security schemes and prompts for authentication
CheckSecuritySchemes(spec)
u, parseErr := url.Parse(swaggerURL)
if parseErr != nil {
u = &url.URL{}
}
// Gets the target server and base path from the specification file
if apiTarget == "" {
if v, ok := spec["swagger"].(string); ok && strings.HasPrefix(v, "2") {
// Swagger (v2)
host, _ := spec["host"].(string)
bp, _ := spec["basePath"].(string)
if bp == "/" {
basePath = ""
} else if bp != "" {
basePath = bp
}
if host != "" && strings.Contains(host, "://") {
apiTarget = host
} else {
if host != "" {
apiTarget = u.Scheme + "://" + host
}
}
} else if v, ok := spec["openapi"].(string); ok && strings.HasPrefix(v, "3") {
// OpenAPI (v3)
if servers, ok := spec["servers"].([]interface{}); ok && len(servers) > 0 {
if len(servers) > 1 {
if !quiet && (os.Args[1] != "endpoints") && apiTarget == "" {
log.Warn("Multiple servers detected in documentation. You can manually set a server to test with the -T flag.\nThe detected servers are as follows:")
for i := range servers {
if srv, ok := servers[i].(map[string]interface{}); ok {
if serverURL, ok := srv["url"].(string); ok {
if strings.Contains(serverURL, "://") {
fmt.Println(serverURL)
} else {
fmt.Println(apiTarget + serverURL)
}
}
}
}
}
} else {
if srv, ok := servers[0].(map[string]interface{}); ok {
if serverURL, ok := srv["url"].(string); ok {
if strings.Contains(serverURL, "://") {
apiTarget = serverURL
} else if serverURL == "/" {
basePath = ""
} else {
basePath = serverURL
apiTarget = u.Scheme + "://" + u.Host
}
}
}
}
}
}
}
// Use the original host at the target if no server found from specification.
if apiTarget == "" {
apiTarget = u.Scheme + "://" + u.Host
}
if os.Args[1] != "endpoints" {
// Prints Title/Description/Version values if they exist
PrintSpecInfo(spec)
}
// Reviews all defined API routes and builds requests as defined
BuildRequestsFromPaths(spec, client)
}
func ResolveRef(spec map[string]interface{}, ref string) map[string]interface{} {
if !strings.HasPrefix(ref, "#") {
return nil