[Feat] Added Figma (PAT) Analyzer (#3974)
* added figma analyzer * fixed lint issue * fixed lint issue (2) * added endpoint config from json * simplified scope extraction and endpoint configuration * updated with lint issue fixes --------- Co-authored-by: Kashif Khan <[email protected]>
This commit is contained in:
co-authored by
Kashif Khan
parent
9a59a7a52a
commit
b2ba219e24
@@ -92,6 +92,7 @@ const (
|
||||
AnalyzerTypeAirtablePat
|
||||
AnalyzerTypeGroq
|
||||
AnalyzerTypeLaunchDarkly
|
||||
AnalyzerTypeFigma
|
||||
// Add new items here with AnalyzerType prefix
|
||||
)
|
||||
|
||||
@@ -129,6 +130,7 @@ var analyzerTypeStrings = map[AnalyzerType]string{
|
||||
AnalyzerTypeAirtablePat: "AirtablePat",
|
||||
AnalyzerTypeGroq: "Groq",
|
||||
AnalyzerTypeLaunchDarkly: "LaunchDarkly",
|
||||
AnalyzerTypeFigma: "Figma",
|
||||
// Add new mappings here
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"files:read": {
|
||||
"url": "https://api.figma.com/v1/me",
|
||||
"method": "GET",
|
||||
"expected_status_code_with_scope": 200,
|
||||
"expected_status_code_without_scope": 403
|
||||
},
|
||||
"library_analytics:read": {
|
||||
"url": "https://api.figma.com/v1/analytics/libraries/0/component/actions",
|
||||
"method": "GET",
|
||||
"expected_status_code_with_scope": 400,
|
||||
"expected_status_code_without_scope": 403
|
||||
},
|
||||
"file_dev_resources:write": {
|
||||
"url": "https://api.figma.com/v1/dev_resources",
|
||||
"method": "POST",
|
||||
"expected_status_code_with_scope": 400,
|
||||
"expected_status_code_without_scope": 403
|
||||
},
|
||||
"file_variables:read": {
|
||||
"url": "https://api.figma.com/v1/files/0/variables/published",
|
||||
"method": "GET",
|
||||
"expected_status_code_with_scope": 404,
|
||||
"expected_status_code_without_scope": 403
|
||||
},
|
||||
"webhooks:write": {
|
||||
"url": "https://api.figma.com/v2/webhooks",
|
||||
"method": "POST",
|
||||
"expected_status_code_with_scope": 400,
|
||||
"expected_status_code_without_scope": 403
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"AnalyzerType":32,"Bindings":[{"Resource":{"Name":"Source Integration","FullyQualifiedName":"1287160752716166666","Type":"user","Metadata":{"email":"[email protected]","img_url":"https://www.gravatar.com/avatar/48da7f448c34d4271a51d2ccf058f473?size=240&default=https%3A%2F%2Fs3-alpha.figma.com%2Fstatic%2Fuser_s_v2.png"},"Parent":null},"Permission":{"Value":"files:read","Parent":null}}],"UnboundedResources":null,"Metadata":null}
|
||||
@@ -0,0 +1,222 @@
|
||||
//go:generate generate_permissions permissions.yaml permissions.go figma
|
||||
|
||||
package figma
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
var _ analyzers.Analyzer = (*Analyzer)(nil)
|
||||
|
||||
type Analyzer struct {
|
||||
Cfg *config.Config
|
||||
}
|
||||
|
||||
func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeFigma }
|
||||
|
||||
type ScopeStatus string
|
||||
|
||||
const (
|
||||
StatusError ScopeStatus = "Error"
|
||||
StatusGranted ScopeStatus = "Granted"
|
||||
StatusDenied ScopeStatus = "Denied"
|
||||
StatusUnverified ScopeStatus = "Unverified"
|
||||
)
|
||||
|
||||
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
|
||||
token, ok := credInfo["token"]
|
||||
if !ok {
|
||||
return nil, errors.New("token not found in credInfo")
|
||||
}
|
||||
info, err := AnalyzePermissions(a.Cfg, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return MapToAnalyzerResult(info), nil
|
||||
}
|
||||
|
||||
func AnalyzeAndPrintPermissions(cfg *config.Config, token string) {
|
||||
info, err := AnalyzePermissions(cfg, token)
|
||||
if err != nil {
|
||||
color.Red("[x] Error : %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
color.Green("[!] Valid Figma Personal Access Token\n\n")
|
||||
PrintUserAndPermissions(info)
|
||||
}
|
||||
|
||||
func AnalyzePermissions(cfg *config.Config, token string) (*secretInfo, error) {
|
||||
client := analyzers.NewAnalyzeClient(cfg)
|
||||
allScopes := getAllScopes()
|
||||
scopeToEndpoints, err := getScopeEndpointsMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var info = &secretInfo{Scopes: map[Scope]ScopeStatus{}}
|
||||
for _, scope := range allScopes {
|
||||
info.Scopes[scope] = StatusUnverified
|
||||
}
|
||||
|
||||
for _, scope := range orderedScopeList {
|
||||
endpoint, err := getScopeEndpoint(scopeToEndpoints, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := callAPIEndpoint(client, token, endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scopeStatus := determineScopeStatus(resp.StatusCode, endpoint)
|
||||
if scopeStatus == StatusGranted {
|
||||
if scope == ScopeFilesRead {
|
||||
if err := json.Unmarshal(body, &info.UserInfo); err != nil {
|
||||
return nil, fmt.Errorf("error decoding user info from response %v", err)
|
||||
}
|
||||
}
|
||||
info.Scopes[scope] = StatusGranted
|
||||
}
|
||||
// If the token does NOT have the scope, response will include all the scopes it does have
|
||||
if scopeStatus == StatusDenied {
|
||||
scopes, ok := extractScopesFromError(body)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("could not extract scopes from error message")
|
||||
}
|
||||
for scope := range info.Scopes {
|
||||
info.Scopes[scope] = StatusDenied
|
||||
}
|
||||
for _, scope := range scopes {
|
||||
info.Scopes[scope] = StatusGranted
|
||||
}
|
||||
// We have enough info to finish analysis
|
||||
break
|
||||
}
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// determineScopeStatus takes the API response status code and uses it along with the expected
|
||||
// status codes to dermine whether the access token has the required scope to perform that action.
|
||||
// It returns a ScopeStatus which can be Granted, Denied, or Unverified.
|
||||
func determineScopeStatus(statusCode int, endpoint endpoint) ScopeStatus {
|
||||
if statusCode == endpoint.ExpectedStatusCodeWithScope || statusCode == http.StatusOK {
|
||||
return StatusGranted
|
||||
}
|
||||
|
||||
if statusCode == endpoint.ExpectedStatusCodeWithoutScope {
|
||||
return StatusDenied
|
||||
}
|
||||
|
||||
// Can not determine scope as the expected error is unknown
|
||||
return StatusUnverified
|
||||
}
|
||||
|
||||
// Matches API response body with expected message pattern in case the token is missing a scope
|
||||
// If the responses match, we can extract all available scopes from the response msg
|
||||
func extractScopesFromError(body []byte) ([]Scope, bool) {
|
||||
filteredBody := filterErrorResponseBody(string(body))
|
||||
re := regexp.MustCompile(`Invalid scope(?:\(s\))?: ([a-zA-Z_:, ]+)\. This endpoint requires.*`)
|
||||
matches := re.FindStringSubmatch(filteredBody)
|
||||
if len(matches) > 1 {
|
||||
scopes := strings.Split(matches[1], ", ")
|
||||
return getScopesFromScopeStrings(scopes), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// The filterErrorResponseBody function cleans the provided "invalid permission" API
|
||||
// response message by removing the characters '"', '[', ']', '\', and '"'.
|
||||
func filterErrorResponseBody(msg string) string {
|
||||
result := strings.ReplaceAll(msg, "\\", "")
|
||||
result = strings.ReplaceAll(result, "\"", "")
|
||||
result = strings.ReplaceAll(result, "[", "")
|
||||
return strings.ReplaceAll(result, "]", "")
|
||||
}
|
||||
|
||||
func MapToAnalyzerResult(info *secretInfo) *analyzers.AnalyzerResult {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := analyzers.AnalyzerResult{
|
||||
AnalyzerType: analyzers.AnalyzerTypeFigma,
|
||||
}
|
||||
var permissions []analyzers.Permission
|
||||
for scope, status := range info.Scopes {
|
||||
if status != StatusGranted {
|
||||
continue
|
||||
}
|
||||
permissions = append(permissions, analyzers.Permission{Value: string(scope)})
|
||||
}
|
||||
userResource := analyzers.Resource{
|
||||
Name: info.UserInfo.Handle,
|
||||
FullyQualifiedName: info.UserInfo.ID,
|
||||
Type: "user",
|
||||
Metadata: map[string]any{
|
||||
"email": info.UserInfo.Email,
|
||||
"img_url": info.UserInfo.ImgURL,
|
||||
},
|
||||
}
|
||||
|
||||
result.Bindings = analyzers.BindAllPermissions(userResource, permissions...)
|
||||
return &result
|
||||
}
|
||||
|
||||
func PrintUserAndPermissions(info *secretInfo) {
|
||||
color.Yellow("[i] User Info:")
|
||||
t1 := table.NewWriter()
|
||||
t1.SetOutputMirror(os.Stdout)
|
||||
t1.AppendHeader(table.Row{"ID", "Handle", "Email", "Image URL"})
|
||||
t1.AppendRow(table.Row{
|
||||
color.GreenString(info.UserInfo.ID),
|
||||
color.GreenString(info.UserInfo.Handle),
|
||||
color.GreenString(info.UserInfo.Email),
|
||||
color.GreenString(info.UserInfo.ImgURL),
|
||||
})
|
||||
t1.SetOutputMirror(os.Stdout)
|
||||
t1.Render()
|
||||
|
||||
color.Yellow("\n[i] Scopes:")
|
||||
t2 := table.NewWriter()
|
||||
t2.AppendHeader(table.Row{"Scope", "Status", "Actions"})
|
||||
for scope, status := range info.Scopes {
|
||||
actions := getScopeActions(scope)
|
||||
rows := []table.Row{}
|
||||
for i, action := range actions {
|
||||
var scopeCell string
|
||||
var statusCell string
|
||||
if i == 0 {
|
||||
scopeCell = color.GreenString(string(scope))
|
||||
statusCell = color.GreenString(string(status))
|
||||
}
|
||||
rows = append(rows, table.Row{scopeCell, statusCell, color.GreenString(action)})
|
||||
}
|
||||
t2.AppendRows(rows)
|
||||
t2.AppendSeparator()
|
||||
}
|
||||
t2.SetOutputMirror(os.Stdout)
|
||||
t2.Render()
|
||||
fmt.Printf("%s: https://www.figma.com/developers/api\n\n", color.GreenString("Ref"))
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package figma
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
//go:embed expected_output.json
|
||||
var expectedOutput []byte
|
||||
|
||||
func TestAnalyzer_Analyze(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
|
||||
if err != nil {
|
||||
t.Fatalf("could not get test secrets from GCP: %s", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
want string // JSON string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
token: testSecrets.MustGetField("FIGMAPERSONALACCESSTOKEN_V2_TOKEN"),
|
||||
name: "valid Figma Personal Access Token",
|
||||
want: string(expectedOutput),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := Analyzer{Cfg: &config.Config{}}
|
||||
got, err := a.Analyze(ctx, map[string]string{"token": tt.token})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
// bindings need to be in the same order to be comparable
|
||||
sortBindings(got.Bindings)
|
||||
|
||||
// Marshal the actual result to JSON
|
||||
gotJSON, err := json.Marshal(got)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to JSON: %s", err)
|
||||
}
|
||||
|
||||
// Parse the expected JSON string
|
||||
var wantObj analyzers.AnalyzerResult
|
||||
if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
|
||||
t.Fatalf("could not unmarshal want JSON string: %s", err)
|
||||
}
|
||||
|
||||
// bindings need to be in the same order to be comparable
|
||||
sortBindings(wantObj.Bindings)
|
||||
|
||||
// Marshal the expected result to JSON (to normalize)
|
||||
wantJSON, err := json.Marshal(wantObj)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to JSON: %s", err)
|
||||
}
|
||||
|
||||
// Compare the JSON strings
|
||||
if string(gotJSON) != string(wantJSON) {
|
||||
// Pretty-print both JSON strings for easier comparison
|
||||
var gotIndented, wantIndented []byte
|
||||
gotIndented, err = json.MarshalIndent(got, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to indented JSON: %s", err)
|
||||
}
|
||||
wantIndented, err = json.MarshalIndent(wantObj, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to indented JSON: %s", err)
|
||||
}
|
||||
t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to sort bindings
|
||||
func sortBindings(bindings []analyzers.Binding) {
|
||||
sort.SliceStable(bindings, func(i, j int) bool {
|
||||
if bindings[i].Resource.Name == bindings[j].Resource.Name {
|
||||
return bindings[i].Permission.Value < bindings[j].Permission.Value
|
||||
}
|
||||
return bindings[i].Resource.Name < bindings[j].Resource.Name
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package figma
|
||||
|
||||
type userInfo struct {
|
||||
ID string `json:"id"`
|
||||
Handle string `json:"handle"`
|
||||
ImgURL string `json:"img_url"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
type secretInfo struct {
|
||||
UserInfo userInfo
|
||||
Scopes map[Scope]ScopeStatus
|
||||
}
|
||||
|
||||
type endpoint struct {
|
||||
URL string `json:"url"`
|
||||
Method string `json:"method"`
|
||||
ExpectedStatusCodeWithScope int `json:"expected_status_code_with_scope"`
|
||||
ExpectedStatusCodeWithoutScope int `json:"expected_status_code_without_scope"`
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
package figma
|
||||
|
||||
import "errors"
|
||||
|
||||
type Permission int
|
||||
|
||||
const (
|
||||
Invalid Permission = iota
|
||||
FilesRead Permission = iota
|
||||
FileVariablesRead Permission = iota
|
||||
FileVariablesWrite Permission = iota
|
||||
FileCommentsWrite Permission = iota
|
||||
FileDevResourcesRead Permission = iota
|
||||
FileDevResourcesWrite Permission = iota
|
||||
LibraryAnalyticsRead Permission = iota
|
||||
WebhooksWrite Permission = iota
|
||||
)
|
||||
|
||||
var (
|
||||
PermissionStrings = map[Permission]string{
|
||||
FilesRead: "files:read",
|
||||
FileVariablesRead: "file_variables:read",
|
||||
FileVariablesWrite: "file_variables:write",
|
||||
FileCommentsWrite: "file_comments:write",
|
||||
FileDevResourcesRead: "file_dev_resources:read",
|
||||
FileDevResourcesWrite: "file_dev_resources:write",
|
||||
LibraryAnalyticsRead: "library_analytics:read",
|
||||
WebhooksWrite: "webhooks:write",
|
||||
}
|
||||
|
||||
StringToPermission = map[string]Permission{
|
||||
"files:read": FilesRead,
|
||||
"file_variables:read": FileVariablesRead,
|
||||
"file_variables:write": FileVariablesWrite,
|
||||
"file_comments:write": FileCommentsWrite,
|
||||
"file_dev_resources:read": FileDevResourcesRead,
|
||||
"file_dev_resources:write": FileDevResourcesWrite,
|
||||
"library_analytics:read": LibraryAnalyticsRead,
|
||||
"webhooks:write": WebhooksWrite,
|
||||
}
|
||||
|
||||
PermissionIDs = map[Permission]int{
|
||||
FilesRead: 1,
|
||||
FileVariablesRead: 2,
|
||||
FileVariablesWrite: 3,
|
||||
FileCommentsWrite: 4,
|
||||
FileDevResourcesRead: 5,
|
||||
FileDevResourcesWrite: 6,
|
||||
LibraryAnalyticsRead: 7,
|
||||
WebhooksWrite: 8,
|
||||
}
|
||||
|
||||
IdToPermission = map[int]Permission{
|
||||
1: FilesRead,
|
||||
2: FileVariablesRead,
|
||||
3: FileVariablesWrite,
|
||||
4: FileCommentsWrite,
|
||||
5: FileDevResourcesRead,
|
||||
6: FileDevResourcesWrite,
|
||||
7: LibraryAnalyticsRead,
|
||||
8: WebhooksWrite,
|
||||
}
|
||||
)
|
||||
|
||||
// ToString converts a Permission enum to its string representation
|
||||
func (p Permission) ToString() (string, error) {
|
||||
if str, ok := PermissionStrings[p]; ok {
|
||||
return str, nil
|
||||
}
|
||||
return "", errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// ToID converts a Permission enum to its ID
|
||||
func (p Permission) ToID() (int, error) {
|
||||
if id, ok := PermissionIDs[p]; ok {
|
||||
return id, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// PermissionFromString converts a string representation to its Permission enum
|
||||
func PermissionFromString(s string) (Permission, error) {
|
||||
if p, ok := StringToPermission[s]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission string")
|
||||
}
|
||||
|
||||
// PermissionFromID converts an ID to its Permission enum
|
||||
func PermissionFromID(id int) (Permission, error) {
|
||||
if p, ok := IdToPermission[id]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission ID")
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
permissions:
|
||||
- files:read
|
||||
- file_variables:read
|
||||
- file_variables:write
|
||||
- file_comments:write
|
||||
- file_dev_resources:read
|
||||
- file_dev_resources:write
|
||||
- library_analytics:read
|
||||
- webhooks:write
|
||||
@@ -0,0 +1,19 @@
|
||||
package figma
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func callAPIEndpoint(client *http.Client, token string, endpoint endpoint) (*http.Response, error) {
|
||||
req, err := http.NewRequest(endpoint.Method, endpoint.URL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("X-FIGMA-TOKEN", token)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package figma
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Scope string
|
||||
|
||||
const (
|
||||
ScopeFilesRead Scope = "files:read"
|
||||
ScopeFileVariablesRead Scope = "file_variables:read"
|
||||
ScopeFileVariablesWrite Scope = "file_variables:write"
|
||||
ScopeFileCommentsWrite Scope = "file_comments:write"
|
||||
ScopeFileDevResourcesRead Scope = "file_dev_resources:read"
|
||||
ScopeFileDevResourcesWrite Scope = "file_dev_resources:write"
|
||||
ScopeLibraryAnalyticsRead Scope = "library_analytics:read"
|
||||
ScopeWebhooksWrite Scope = "webhooks:write"
|
||||
)
|
||||
|
||||
// This list orders the scope in which they must be tested
|
||||
var orderedScopeList = []Scope{
|
||||
ScopeFilesRead,
|
||||
ScopeLibraryAnalyticsRead,
|
||||
ScopeFileDevResourcesWrite,
|
||||
ScopeFileVariablesRead,
|
||||
ScopeWebhooksWrite,
|
||||
}
|
||||
|
||||
var scopeToActions = map[Scope][]string{
|
||||
ScopeFilesRead: {
|
||||
"Get user info",
|
||||
"Read files",
|
||||
"Read projects",
|
||||
"Read users",
|
||||
"Read versions",
|
||||
"Read comments",
|
||||
"Read components & styles",
|
||||
"Read webhooks",
|
||||
},
|
||||
ScopeFileVariablesRead: {
|
||||
"Read file variables",
|
||||
},
|
||||
ScopeFileVariablesWrite: {
|
||||
"Write file variables",
|
||||
},
|
||||
ScopeFileCommentsWrite: {
|
||||
"Post comments",
|
||||
"Delete comments",
|
||||
"Post comment reactions",
|
||||
"Delete comment reactions",
|
||||
},
|
||||
ScopeFileDevResourcesRead: {
|
||||
"Read file dev resources",
|
||||
},
|
||||
ScopeFileDevResourcesWrite: {
|
||||
"Write file dev resources",
|
||||
},
|
||||
ScopeLibraryAnalyticsRead: {
|
||||
"Read design system analytics",
|
||||
},
|
||||
ScopeWebhooksWrite: {
|
||||
"Create webhooks",
|
||||
"Manage webhooks",
|
||||
},
|
||||
}
|
||||
|
||||
var scopeStringToScope map[string]Scope
|
||||
|
||||
//go:embed endpoints.json
|
||||
var endpointsConfig []byte
|
||||
|
||||
func init() {
|
||||
scopeStringToScope = map[string]Scope{
|
||||
string(ScopeFilesRead): ScopeFilesRead,
|
||||
string(ScopeFileVariablesRead): ScopeFileVariablesRead,
|
||||
string(ScopeFileVariablesWrite): ScopeFileVariablesWrite,
|
||||
string(ScopeFileCommentsWrite): ScopeFileCommentsWrite,
|
||||
string(ScopeFileDevResourcesRead): ScopeFileDevResourcesRead,
|
||||
string(ScopeFileDevResourcesWrite): ScopeFileDevResourcesWrite,
|
||||
string(ScopeLibraryAnalyticsRead): ScopeLibraryAnalyticsRead,
|
||||
string(ScopeWebhooksWrite): ScopeWebhooksWrite,
|
||||
}
|
||||
}
|
||||
|
||||
func getScopeActions(scope Scope) []string {
|
||||
return scopeToActions[scope]
|
||||
}
|
||||
|
||||
func getScopeEndpointsMap() (map[Scope]endpoint, error) {
|
||||
var scopeToEndpoints map[Scope]endpoint
|
||||
if err := json.Unmarshal(endpointsConfig, &scopeToEndpoints); err != nil {
|
||||
return nil, errors.New("failed to unmarshal endpoints.json: " + err.Error())
|
||||
}
|
||||
return scopeToEndpoints, nil
|
||||
}
|
||||
|
||||
func getScopeEndpoint(scopeToEndpoint map[Scope]endpoint, scope Scope) (endpoint, error) {
|
||||
if endpoint, ok := scopeToEndpoint[scope]; ok {
|
||||
return endpoint, nil
|
||||
}
|
||||
return endpoint{}, errors.New("invalid scope or endpoint doesn't exist")
|
||||
}
|
||||
|
||||
func getScopesFromScopeStrings(scopeStrings []string) []Scope {
|
||||
var scopes []Scope
|
||||
for _, scopeString := range scopeStrings {
|
||||
if scope, ok := scopeStringToScope[scopeString]; ok {
|
||||
scopes = append(scopes, scope)
|
||||
}
|
||||
}
|
||||
return scopes
|
||||
}
|
||||
|
||||
func getAllScopes() []Scope {
|
||||
return []Scope{
|
||||
ScopeFilesRead,
|
||||
ScopeFileVariablesRead,
|
||||
ScopeFileVariablesWrite,
|
||||
ScopeFileCommentsWrite,
|
||||
ScopeFileDevResourcesRead,
|
||||
ScopeFileDevResourcesWrite,
|
||||
ScopeLibraryAnalyticsRead,
|
||||
ScopeWebhooksWrite,
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/digitalocean"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/dockerhub"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/elevenlabs"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/figma"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/gitlab"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/groq"
|
||||
@@ -115,5 +116,7 @@ func Run(keyType string, secretInfo SecretInfo) {
|
||||
groq.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
case "launchdarkly":
|
||||
launchdarkly.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
case "figma":
|
||||
figma.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
} else {
|
||||
s1.SetVerificationError(err, resMatch)
|
||||
}
|
||||
if s1.Verified {
|
||||
s1.AnalysisInfo = map[string]string{"token": resMatch}
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, s1)
|
||||
|
||||
@@ -79,6 +79,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
} else {
|
||||
s1.SetVerificationError(err, resMatch)
|
||||
}
|
||||
if s1.Verified {
|
||||
s1.AnalysisInfo = map[string]string{"token": resMatch}
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, s1)
|
||||
|
||||
Reference in New Issue
Block a user