Compare commits

...
59 Commits
Author SHA1 Message Date
moloch-- 71cd8fa610 Skip generate tests if there's no edits to related code 2026-01-30 10:35:16 -08:00
moloch-- e9fdcc1285 Improve hex-edit ux 2026-01-30 10:30:15 -08:00
moloch-- 870c995ae2 Add hexedit feature 2026-01-30 10:20:56 -08:00
Joe 15d7e7c4d1 Merge pull request #2150 from BishopFox/feature/chtimes
Expand time formats supported by chtimes
2026-01-30 10:50:56 -07:00
moloch-- 750cdfce0a Expand time formats supported by chtimes 2026-01-30 09:41:55 -08:00
Joe 0f128a22b3 Merge pull request #1807 from navsec/socks5-stability
SOCKS5 Stability Improvements for HTTP/mTLS Transports
2026-01-30 10:07:13 -07:00
Joe 3296a8f483 Merge branch 'master' into socks5-stability
Signed-off-by: Joe <[email protected]>
2026-01-30 10:07:05 -07:00
Joe b77ade2bb9 Merge pull request #2148 from BishopFox/fix/info
Fix/info
2026-01-30 08:47:32 -07:00
moloch-- 41dc54d736 Implement mount for darwin 2026-01-29 22:22:59 -08:00
moloch-- 8a0694cb38 Add mount on darwin 2026-01-29 22:08:28 -08:00
moloch-- 52c0618bc3 Add os version implementations for linux/darwin 2026-01-29 21:50:16 -08:00
moloch-- e2e1912628 Add search to edit cmd 2026-01-29 21:01:16 -08:00
moloch-- 36794f1b07 Implement skip to line number 2026-01-29 20:50:11 -08:00
moloch-- ccd8f75b03 v1.6.9 2026-01-29 20:36:45 -08:00
moloch-- dc89afe404 Add optional line numbers 2026-01-29 20:32:15 -08:00
moloch-- 9db1f3a967 Add syntax highlighting 2026-01-29 20:22:23 -08:00
moloch-- a2ab3aede6 Implement 'edit' command 2026-01-29 20:07:51 -08:00
Joe 545fc27d03 Merge pull request #2147 from BishopFox/feature/certs-db
Feature/certs db
2026-01-29 09:59:10 -07:00
moloch-- 5bf65aa2b0 Fix notification indentation 2026-01-29 08:51:01 -08:00
Joe ebaf07ff1f Merge branch 'master' into feature/certs-db 2026-01-29 09:16:35 -07:00
moloch-- fc95d3e354 Fix notifications build on windows 2026-01-29 08:15:32 -08:00
moloch-- 88dca9337b Fix notifications docs anchors 2026-01-29 08:11:17 -08:00
moloch-- 44d9f8d5aa Update cli to work with db CAs 2026-01-29 08:01:35 -08:00
moloch-- f76e9e367d Move CAs to db 2026-01-29 07:56:20 -08:00
moloch-- 69fa4143f6 Improve logging for invalid configs 2026-01-29 07:08:34 -08:00
moloch-- dcbda0c64e Improve logging for notifications 2026-01-29 06:50:18 -08:00
moloch-- 3db48408af Initial notifications implementation 2026-01-29 06:44:11 -08:00
moloch-- 3c8edb7294 v1.6.8 2026-01-28 20:14:36 -08:00
moloch-- 2e2206e0ae Fix emtpy prompt after event 2026-01-28 19:58:08 -08:00
Joe a43648a24a Merge pull request #2146 from BishopFox/fix/websites-css
Fix/websites css
2026-01-28 19:50:20 -07:00
moloch-- f65f98bf65 Improve websites content-type sniffing issue #2033 2026-01-28 18:14:27 -08:00
Joe 26d14e15ad Merge pull request #2144 from BishopFox/fix/update
Fix/update
2026-01-28 19:07:58 -07:00
moloch-- cee2cc76c4 Convert tui-settings to yaml 2026-01-28 18:06:41 -08:00
moloch-- daa33b6301 Convert server configs to yaml syntax, with backwards compatibility 2026-01-28 17:59:34 -08:00
moloch-- ef52973536 Convert configs to yml 2026-01-28 17:39:14 -08:00
moloch-- 6a7bd0803a Update --save help 2026-01-28 17:21:35 -08:00
moloch-- 6c70c1c1e6 Add signature validation logic 2026-01-28 17:20:24 -08:00
moloch-- ddc222b8a2 Improvements to update 2026-01-28 17:07:47 -08:00
moloch-- 5506a582ac Add SliverPublicKey 2026-01-28 16:45:32 -08:00
Joe 63d872c98b Merge pull request #2142 from BishopFox/fix/sgn
Fix/sgn
2026-01-28 17:31:46 -07:00
moloch-- ef9f5dde0d Fix unit tests 2026-01-28 07:21:38 -08:00
moloch-- e35b7fdb97 Update sgn to v0.0.5 2026-01-27 21:28:21 -08:00
moloch-- efbbf382eb Merge branch 'master' into fix/sgn 2026-01-27 21:26:42 -08:00
moloch-- 0b8d00900d v1.6.7 2026-01-27 21:06:39 -08:00
moloch-- 9077596f25 Damn it Claude 2026-01-27 21:04:30 -08:00
moloch-- 0a8104a32e Damn it Claude 2026-01-27 21:03:25 -08:00
moloch-- 15a0ba4c1e Damn it Claude 2026-01-27 21:01:55 -08:00
moloch-- d452c8a676 Write .bin even if sgn fails 2026-01-27 20:49:28 -08:00
Joe fb74b491fa Merge pull request #2141 from BishopFox/fix/console-inputs
Update readline/console deps
2026-01-27 21:38:20 -07:00
moloch-- 2af6e11adc Update readline/console deps 2026-01-27 19:55:18 -08:00
Joe a3a98858fd Merge pull request #2135 from BishopFox/fix/sqlite
Add sqlite pragmas to db config
2026-01-21 22:28:56 -07:00
Joe 3c46cf2210 Merge branch 'master' into socks5-stability 2025-10-24 11:41:27 -07:00
Ronan Kervella edcdfb5937 Merge branch 'master' into socks5-stability 2024-12-04 09:36:59 -10:00
Ronan Kervella f81efdb38c Merge branch 'master' into socks5-stability 2024-12-02 15:40:40 -08:00
Ronan Kervella db34ded0a1 Merge branch 'master' into socks5-stability 2024-11-18 15:06:32 -08:00
navsec 8a7a92a9b9 Refine implant/server signaling for tunnel closures 2024-11-18 17:02:15 -06:00
navsec 3eda32f9d7 Performance Optimizations and Implant SOCKS Changes 2024-11-17 15:01:04 -06:00
navsec c18c193ea7 Improve SOCKS lifecycle mangement and resolve leak 2024-11-06 23:24:44 -06:00
navsec 79a8758448 SOCKS5 Stability Improvements for HTTP/mTLS 2024-10-31 12:46:40 -05:00
4359 changed files with 1238231 additions and 5674 deletions

No files matched your search

+28 -2
View File
@@ -48,8 +48,21 @@ jobs:
- name: Make Linux (amd64)
run: make linux-amd64
- name: Determine go-tests flags
id: go-tests-flags
shell: bash
run: |
set -euo pipefail
base_sha="${{ github.event.pull_request.base.sha }}"
head_sha="${{ github.sha }}"
if git diff --name-only "$base_sha" "$head_sha" | grep -qE '^(implant/|server/gogo/|server/generate/)'; then
echo "args=" >> "$GITHUB_OUTPUT"
else
echo "args=--skip-generate" >> "$GITHUB_OUTPUT"
fi
- name: Test Linux
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh ${{ steps.go-tests-flags.outputs.args }}
macos-build-test:
@@ -74,8 +87,21 @@ jobs:
- name: Make MacOS (arm64)
run: make macos-arm64
- name: Determine go-tests flags
id: go-tests-flags
shell: bash
run: |
set -euo pipefail
base_sha="${{ github.event.pull_request.base.sha }}"
head_sha="${{ github.sha }}"
if git diff --name-only "$base_sha" "$head_sha" | grep -qE '^(implant/|server/gogo/|server/generate/)'; then
echo "args=" >> "$GITHUB_OUTPUT"
else
echo "args=--skip-generate" >> "$GITHUB_OUTPUT"
fi
- name: Test MacOS
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh ${{ steps.go-tests-flags.outputs.args }}
clients-build:
name: Clients Build
+3
View File
@@ -29,11 +29,14 @@ MIN_SUPPORTED_GO_MAJOR_VERSION = 1
MIN_SUPPORTED_GO_MINOR_VERSION = 24
GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MIN_SUPPORTED_GO_MAJOR_VERSION).$(MIN_SUPPORTED_GO_MINOR_VERSION)
SLIVER_PUBLIC_KEY ?= RWTZPg959v3b7tLG7VzKHRB1/QT+d3c71Uzetfa44qAoX5rH7mGoQTTR
ARMORY_PUBLIC_KEY ?= RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L
ARMORY_REPO_URL ?= https://api.github.com/repos/sliverarmory/armory/releases
CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
SLIVER_UPDATE_PKG = github.com/bishopfox/sliver/client/command/update
LDFLAGS = -ldflags "-s -w \
-X $(SLIVER_UPDATE_PKG).SliverPublicKey=$(SLIVER_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
+10 -5
View File
@@ -34,12 +34,19 @@ const (
SliverClientDirName = ".sliver-client"
versionFileName = "version"
envVarName = "SLIVER_CLIENT_ROOT_DIR"
)
// GetRootAppDir - Get the Sliver app dir ~/.sliver-client/
func GetRootAppDir() string {
user, _ := user.Current()
dir := filepath.Join(user.HomeDir, SliverClientDirName)
value := os.Getenv(envVarName)
var dir string
if len(value) == 0 {
user, _ := user.Current()
dir = filepath.Join(user.HomeDir, SliverClientDirName)
} else {
dir = value
}
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0700)
if err != nil {
@@ -116,7 +123,5 @@ under certain conditions; type 'licenses' for details.`)
}
saveAssetVersion(appDir)
}
if _, err := os.Stat(filepath.Join(appDir, settingsFileName)); os.IsNotExist(err) {
SaveSettings(nil)
}
_, _ = LoadSettings()
}
+27
View File
@@ -0,0 +1,27 @@
package assets
import (
"os"
"path/filepath"
"strings"
)
func legacyBackupPath(legacyPath string) string {
dir := filepath.Dir(legacyPath)
base := filepath.Base(legacyPath)
name := strings.TrimSuffix(base, filepath.Ext(base))
return filepath.Join(dir, "."+name+".json-old")
}
func renameLegacyConfig(legacyPath string) error {
if _, err := os.Stat(legacyPath); os.IsNotExist(err) {
return nil
}
backupPath := legacyBackupPath(legacyPath)
if _, err := os.Stat(backupPath); err == nil {
if err := os.Remove(backupPath); err != nil {
return err
}
}
return os.Rename(legacyPath, backupPath)
}
+39 -16
View File
@@ -22,35 +22,58 @@ import (
"encoding/json"
"os"
"path/filepath"
"gopkg.in/yaml.v3"
)
const (
settingsFileName = "tui-settings.json"
settingsFileName = "tui-settings.yaml"
settingsLegacyFileName = "tui-settings.json"
)
// ClientSettings - Client JSON config
type ClientSettings struct {
TableStyle string `json:"tables"`
AutoAdult bool `json:"autoadult"`
BeaconAutoResults bool `json:"beacon_autoresults"`
SmallTermWidth int `json:"small_term_width"`
AlwaysOverflow bool `json:"always_overflow"`
VimMode bool `json:"vim_mode"`
UserConnect bool `json:"user_connect"`
ConsoleLogs bool `json:"console_logs"`
TableStyle string `json:"tables" yaml:"tables"`
AutoAdult bool `json:"autoadult" yaml:"autoadult"`
BeaconAutoResults bool `json:"beacon_autoresults" yaml:"beacon_autoresults"`
SmallTermWidth int `json:"small_term_width" yaml:"small_term_width"`
AlwaysOverflow bool `json:"always_overflow" yaml:"always_overflow"`
VimMode bool `json:"vim_mode" yaml:"vim_mode"`
UserConnect bool `json:"user_connect" yaml:"user_connect"`
ConsoleLogs bool `json:"console_logs" yaml:"console_logs"`
}
// LoadSettings - Load the client settings from disk
func LoadSettings() (*ClientSettings, error) {
rootDir, _ := filepath.Abs(GetRootAppDir())
data, err := os.ReadFile(filepath.Join(rootDir, settingsFileName))
if err != nil {
settingsPath := filepath.Join(rootDir, settingsFileName)
legacyPath := filepath.Join(rootDir, settingsLegacyFileName)
settings := defaultSettings()
migratedLegacy := false
data, err := os.ReadFile(settingsPath)
if err == nil {
if err = yaml.Unmarshal(data, settings); err != nil {
return defaultSettings(), err
}
} else if !os.IsNotExist(err) {
return defaultSettings(), err
} else if data, err = os.ReadFile(legacyPath); err == nil {
if err = json.Unmarshal(data, settings); err != nil {
return defaultSettings(), err
}
migratedLegacy = true
} else if !os.IsNotExist(err) {
return defaultSettings(), err
}
settings := defaultSettings()
err = json.Unmarshal(data, settings)
if err != nil {
return defaultSettings(), err
if err := SaveSettings(settings); err != nil {
return settings, err
}
if migratedLegacy {
if err := renameLegacyConfig(legacyPath); err != nil {
return settings, err
}
}
return settings, nil
}
@@ -73,7 +96,7 @@ func SaveSettings(settings *ClientSettings) error {
if settings == nil {
settings = defaultSettings()
}
data, err := json.MarshalIndent(settings, "", " ")
data, err := yaml.Marshal(settings)
if err != nil {
return err
}
+115
View File
@@ -0,0 +1,115 @@
package assets
import (
"encoding/json"
"os"
"path/filepath"
"testing"
)
func TestLoadSettingsParsesYAML(t *testing.T) {
t.Setenv(envVarName, t.TempDir())
rootDir, _ := filepath.Abs(GetRootAppDir())
settingsPath := filepath.Join(rootDir, settingsFileName)
data := []byte(`tables: "Compact"
autoadult: true
beacon_autoresults: false
small_term_width: 120
always_overflow: true
vim_mode: true
user_connect: true
console_logs: false
`)
if err := os.WriteFile(settingsPath, data, 0o600); err != nil {
t.Fatalf("failed to write yaml settings: %v", err)
}
settings, err := LoadSettings()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if settings.TableStyle != "Compact" {
t.Fatalf("expected TableStyle %q, got %q", "Compact", settings.TableStyle)
}
if !settings.AutoAdult {
t.Fatalf("expected AutoAdult true")
}
if settings.BeaconAutoResults {
t.Fatalf("expected BeaconAutoResults false")
}
if settings.SmallTermWidth != 120 {
t.Fatalf("expected SmallTermWidth %d, got %d", 120, settings.SmallTermWidth)
}
if !settings.AlwaysOverflow {
t.Fatalf("expected AlwaysOverflow true")
}
if !settings.VimMode {
t.Fatalf("expected VimMode true")
}
if !settings.UserConnect {
t.Fatalf("expected UserConnect true")
}
if settings.ConsoleLogs {
t.Fatalf("expected ConsoleLogs false")
}
}
func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
t.Setenv(envVarName, t.TempDir())
rootDir, _ := filepath.Abs(GetRootAppDir())
legacyPath := filepath.Join(rootDir, settingsLegacyFileName)
legacy := &ClientSettings{
TableStyle: "Legacy",
AutoAdult: true,
BeaconAutoResults: false,
SmallTermWidth: 200,
AlwaysOverflow: true,
VimMode: true,
UserConnect: true,
ConsoleLogs: false,
}
data, err := json.MarshalIndent(legacy, "", " ")
if err != nil {
t.Fatalf("failed to marshal legacy settings: %v", err)
}
if err := os.WriteFile(legacyPath, data, 0o600); err != nil {
t.Fatalf("failed to write legacy settings: %v", err)
}
settings, err := LoadSettings()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if settings.TableStyle != "Legacy" {
t.Fatalf("expected TableStyle %q, got %q", "Legacy", settings.TableStyle)
}
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
t.Fatalf("expected yaml settings to exist: %v", err)
}
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
t.Fatalf("expected legacy settings to be renamed: %v", err)
}
if _, err := os.Stat(legacyBackupPath(legacyPath)); err != nil {
t.Fatalf("expected legacy backup to exist: %v", err)
}
}
func TestLoadSettingsWritesDefault(t *testing.T) {
t.Setenv(envVarName, t.TempDir())
settings, err := LoadSettings()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if settings.TableStyle != "SliverDefault" {
t.Fatalf("expected default TableStyle %q, got %q", "SliverDefault", settings.TableStyle)
}
rootDir, _ := filepath.Abs(GetRootAppDir())
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
t.Fatalf("expected default settings to be written: %v", err)
}
}
+1
View File
@@ -25,6 +25,7 @@ Shared registration glue, scaffolding, and helpers for Sliver client console com
- `creds/` – Implements the 'creds' command group for the Sliver client console.
- `cursed/` – Implements the 'cursed' command group for the Sliver client console.
- `dllhijack/` – Implements the 'dllhijack' command group for the Sliver client console.
- `edit/` – Implements the 'edit' command for the Sliver client console.
- `environment/` – Implements the 'environment' command group for the Sliver client console.
- `exec/` – Implements the 'exec' command group for the Sliver client console.
- `exit/` – Implements the 'exit' command group for the Sliver client console.
+2
View File
@@ -6,5 +6,7 @@ Implements the 'certificates' command group for the Sliver client console.
## Go Files
- `authorities.go` – Retrieves certificate authority metadata from the server and prints it in a table.
- `completions.go` – Provides tab completion helpers for certificate common names.
- `certificates.go` – Retrieves certificate metadata from the server, formats expiration status, and prints results to the console.
- `commands.go` – Registers the certificates command and binds the flags needed to query certificate inventories.
@@ -0,0 +1,90 @@
package certificates
import (
"context"
"fmt"
"time"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
"golang.org/x/term"
)
func CertificateAuthoritiesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
authorityInfo, err := con.Rpc.GetCertificateAuthorityInfo(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintErrorf("could not get certificate authority information from database: %s", err.Error())
return
}
printCertificateAuthorityInfo(con, authorityInfo.Info)
}
func printCertificateAuthorityInfo(con *console.SliverClient, authData []*clientpb.CertificateAuthorityData) {
width, _, err := term.GetSize(0)
if err != nil {
width = 999
}
if len(authData) == 0 {
con.PrintWarnf("There are no certificate authorities in the database.\n")
return
}
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
wideTermWidth := con.Settings.SmallTermWidth < width
if wideTermWidth {
tw.AppendHeader(table.Row{
"ID",
"Common Name",
"Creation Time",
"Authority Type",
"Key Algorithm",
"Validity Start",
"Expires",
})
} else {
tw.AppendHeader(table.Row{
"ID",
"Common Name",
"Expires",
})
}
for _, authority := range authData {
rowColor := console.Normal
expiry, err := time.Parse(timeFormat, authority.ValidityExpiry)
if err == nil {
rowColor = checkCertExpiry(expiry)
}
if wideTermWidth {
tw.AppendRow(table.Row{
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ID),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CN),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CreationTime),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.Type),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.KeyAlgorithm),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityStart),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityExpiry),
})
} else {
tw.AppendRow(table.Row{
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ID),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CN),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityExpiry),
})
}
}
tw.SortBy([]table.SortBy{{Name: "Expires", Mode: table.Dsc}})
con.Printf("%s\n", tw.Render())
}
+14
View File
@@ -23,6 +23,7 @@ import (
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -44,6 +45,19 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.BoolP("server", "s", false, "Show server certificates")
f.StringP("cn", "c", "", "Show certificate information for a provided common name")
})
flags.BindFlagCompletions(certificatesCmd, func(comp *carapace.ActionMap) {
(*comp)["cn"] = CertificateCommonNameCompleter(con)
})
registerCertificateCNFlagCompletion(certificatesCmd, "cn", con)
authoritiesCmd := &cobra.Command{
Use: "authorities",
Short: "List certificate authorities",
Run: func(cmd *cobra.Command, args []string) {
CertificateAuthoritiesCmd(cmd, con, args)
},
}
certificatesCmd.AddCommand(authoritiesCmd)
return []*cobra.Command{
certificatesCmd,
@@ -0,0 +1,96 @@
package certificates
import (
"context"
"sort"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
)
// CertificateCommonNameCompleter completes common names for stored certificates.
func CertificateCommonNameCompleter(con *console.SliverClient) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
if con == nil || con.Rpc == nil {
return carapace.ActionMessage("not connected")
}
values, err := certificateCommonNameValues(con)
if err != nil {
return carapace.ActionMessage("failed to list certificates %s", err)
}
if len(values) == 0 {
return carapace.ActionMessage("no certificates")
}
return carapace.ActionValues(values...).Tag("common name").Usage("certificate common name")
})
}
func registerCertificateCNFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
if cmd == nil {
return
}
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
return
}
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
return
}
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
values, err := certificateCommonNameValues(con)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
})
}
func certificateCommonNameValues(con *console.SliverClient) ([]string, error) {
if con == nil || con.Rpc == nil {
return nil, nil
}
certInfo, err := con.Rpc.GetCertificateInfo(context.Background(), &clientpb.CertificatesReq{
CategoryFilters: AllTransports | AllRoles,
})
if err != nil {
return nil, err
}
unique := map[string]struct{}{}
for _, cert := range certInfo.Info {
if cert.CN == "" {
continue
}
unique[cert.CN] = struct{}{}
}
values := make([]string, 0, len(unique))
for name := range unique {
values = append(values, name)
}
sort.Strings(values)
return values, nil
}
func filterCompletionValues(values []string, prefix string) []string {
if prefix == "" {
return values
}
filtered := make([]string, 0, len(values))
for _, value := range values {
candidate := value
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
candidate = value[:tab]
}
if strings.HasPrefix(candidate, prefix) {
filtered = append(filtered, value)
}
}
return filtered
}
+38
View File
@@ -0,0 +1,38 @@
package edit
import (
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// Commands returns the edit command.
func Commands(con *console.SliverClient) []*cobra.Command {
editCmd := &cobra.Command{
Use: consts.EditStr,
Short: "Edit a remote text file",
Long: help.GetHelpFor([]string{consts.EditStr}),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
EditCmd(cmd, con, args)
},
GroupID: consts.FilesystemHelpGroup,
}
flags.Bind("", false, editCmd, func(f *pflag.FlagSet) {
f.String("syntax", "", "syntax highlighting lexer name (optional)")
f.Bool("syntax-select", false, "prompt to select syntax highlighting lexer")
f.Bool("line-numbers", false, "show line numbers")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
carapace.Gen(editCmd).PositionalCompletion(
carapace.ActionValues().Usage("path to the remote file to edit"),
)
return []*cobra.Command{editCmd}
}
+380
View File
@@ -0,0 +1,380 @@
package edit
import (
"context"
"errors"
"fmt"
"sort"
"strings"
"time"
"unicode/utf8"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/bishopfox/sliver/client/command/filesystem"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util/encoders"
tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra"
"google.golang.org/protobuf/proto"
)
const beaconPollInterval = 1 * time.Second
// EditCmd downloads a remote text file, opens it in a TUI editor, and optionally uploads changes.
func EditCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
remotePath := strings.TrimSpace(args[0])
if remotePath == "" {
con.PrintErrorf("Missing parameter: remote path\n")
return
}
ctx, cancel := con.GrpcContext(cmd)
defer cancel()
ctrl := make(chan bool)
con.SpinUntil(fmt.Sprintf("Downloading %s ...", remotePath), ctrl)
download, err := downloadRemote(ctx, con, cmd, remotePath)
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if download.Response != nil && download.Response.Err != "" {
con.PrintErrorf("%s\n", download.Response.Err)
return
}
if !download.Exists {
con.PrintErrorf("Remote file does not exist: %s\n", remotePath)
return
}
if download.IsDir {
con.PrintErrorf("Remote path is a directory: %s\n", download.Path)
return
}
data := download.Data
if download.Encoder == "gzip" {
data, err = new(encoders.Gzip).Decode(download.Data)
if err != nil {
con.PrintErrorf("Decoding failed %s\n", err)
return
}
}
if !isText(data) {
con.PrintErrorf("Refusing to edit non-text file: %s\n", download.Path)
return
}
path := download.Path
if path == "" {
path = remotePath
}
lineEnding := detectLineEnding(data)
content := normalizeLineEndings(data)
lexer, lexerName, err := resolveSyntax(cmd, path, string(data))
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
showLineNumbers, _ := cmd.Flags().GetBool("line-numbers")
for {
result, err := runEditor(content, path, lexer, lexerName, showLineNumbers)
if err != nil {
con.PrintErrorf("Editor error: %s\n", err)
return
}
content = result.Content
if result.Action == actionNone {
return
}
if !result.Modified {
if result.Action == actionSaveQuit {
con.PrintInfof("No changes to upload for %s\n", path)
}
return
}
switch result.Action {
case actionSaveQuit:
uploadNow, err := confirm("Upload changes to remote file (overwrite)?")
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if !uploadNow {
keepEditing, err := confirm("Continue editing?")
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if keepEditing {
continue
}
return
}
uploadData := applyLineEnding(content, lineEnding)
if err := uploadEditedContent(cmd, con, path, uploadData); err != nil {
con.PrintErrorf("%s\n", err)
}
return
case actionQuit:
if result.Force {
return
}
discard, err := confirm("Discard changes?")
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if discard {
return
}
}
}
}
func runEditor(content, filename string, lexer chroma.Lexer, lexerName string, showLineNumbers bool) (editorResult, error) {
model := newEditorModel(content, filename, lexer, lexerName, showLineNumbers)
program := tea.NewProgram(model, tea.WithAltScreen())
finalModel, err := program.Run()
if err != nil {
return editorResult{}, err
}
editor, ok := finalModel.(*editorModel)
if !ok {
return editorResult{}, fmt.Errorf("unexpected editor state")
}
return editor.result(), nil
}
func downloadRemote(ctx context.Context, con *console.SliverClient, cmd *cobra.Command, path string) (*sliverpb.Download, error) {
download, err := con.Rpc.Download(ctx, &sliverpb.DownloadReq{
Request: con.ActiveTarget.Request(cmd),
Path: path,
RestrictedToFile: true,
})
if err != nil {
return nil, err
}
if download.Response != nil && download.Response.Async {
taskID := download.Response.TaskID
download = &sliverpb.Download{}
if err := waitForBeaconTaskResponse(ctx, con, taskID, download); err != nil {
return nil, err
}
}
return download, nil
}
func uploadEditedContent(cmd *cobra.Command, con *console.SliverClient, path string, data []byte) error {
ctx, cancel := con.GrpcContext(cmd)
defer cancel()
uploadData, err := new(encoders.Gzip).Encode(data)
if err != nil {
return err
}
ctrl := make(chan bool)
con.SpinUntil(fmt.Sprintf("Uploading %s ...", path), ctrl)
upload, err := con.Rpc.Upload(ctx, &sliverpb.UploadReq{
Request: con.ActiveTarget.Request(cmd),
Path: path,
Data: uploadData,
Encoder: "gzip",
IsDirectory: false,
Overwrite: true,
})
if err == nil && upload.Response != nil && upload.Response.Async {
taskID := upload.Response.TaskID
upload = &sliverpb.Upload{}
err = waitForBeaconTaskResponse(ctx, con, taskID, upload)
}
ctrl <- true
<-ctrl
if err != nil {
return err
}
filesystem.PrintUpload(upload, con)
return nil
}
func waitForBeaconTaskResponse(ctx context.Context, con *console.SliverClient, taskID string, out proto.Message) error {
task, err := waitForBeaconTask(ctx, con, taskID)
if err != nil {
return err
}
state := strings.ToLower(task.State)
if state != "completed" {
if state == "" {
state = "unknown"
}
return fmt.Errorf("beacon task %s %s", task.ID, state)
}
if len(task.Response) == 0 {
return fmt.Errorf("beacon task %s returned empty response", task.ID)
}
return proto.Unmarshal(task.Response, out)
}
func waitForBeaconTask(ctx context.Context, con *console.SliverClient, taskID string) (*clientpb.BeaconTask, error) {
ticker := time.NewTicker(beaconPollInterval)
defer ticker.Stop()
for {
task, err := con.Rpc.GetBeaconTaskContent(ctx, &clientpb.BeaconTask{ID: taskID})
if err != nil {
return nil, err
}
state := strings.ToLower(task.State)
if state == "completed" || state == "failed" || state == "canceled" {
return task, nil
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-ticker.C:
}
}
}
func resolveSyntax(cmd *cobra.Command, path string, content string) (chroma.Lexer, string, error) {
syntaxName, _ := cmd.Flags().GetString("syntax")
selectSyntax, _ := cmd.Flags().GetBool("syntax-select")
syntaxName = strings.TrimSpace(syntaxName)
if strings.EqualFold(syntaxName, "auto") {
syntaxName = ""
}
if syntaxName != "" {
if strings.EqualFold(syntaxName, "none") {
return nil, "none", nil
}
lexer := lexers.Get(syntaxName)
if lexer == nil {
return nil, "", fmt.Errorf("unknown syntax lexer: %s", syntaxName)
}
name := lexer.Config().Name
if name == "" {
name = syntaxName
}
return lexer, name, nil
}
if selectSyntax {
options := syntaxOptions()
selection := "auto"
if err := forms.Select("Syntax highlighting", options, &selection); err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return detectSyntax(path, content)
}
return nil, "", err
}
if strings.EqualFold(selection, "none") {
return nil, "none", nil
}
if strings.EqualFold(selection, "auto") {
return detectSyntax(path, content)
}
lexer := lexers.Get(selection)
if lexer == nil {
return nil, "", fmt.Errorf("unknown syntax lexer: %s", selection)
}
name := lexer.Config().Name
if name == "" {
name = selection
}
return lexer, name, nil
}
return detectSyntax(path, content)
}
func detectSyntax(path string, content string) (chroma.Lexer, string, error) {
lexer := lexers.Match(path)
if lexer == nil {
lexer = lexers.Analyse(content)
}
if lexer == nil {
lexer = lexers.Fallback
}
name := lexer.Config().Name
if name == "" {
name = "auto"
}
return lexer, name, nil
}
func syntaxOptions() []string {
names := lexers.Names(true)
sort.Strings(names)
options := make([]string, 0, len(names)+2)
options = append(options, "auto", "none")
options = append(options, names...)
return options
}
func confirm(title string) (bool, error) {
answer := false
if err := forms.Confirm(title, &answer); err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return false, nil
}
return false, err
}
return answer, nil
}
func detectLineEnding(data []byte) string {
if strings.Contains(string(data), "\r\n") {
return "\r\n"
}
return "\n"
}
func normalizeLineEndings(data []byte) string {
content := strings.ReplaceAll(string(data), "\r\n", "\n")
content = strings.ReplaceAll(content, "\r", "\n")
return content
}
func applyLineEnding(content, lineEnding string) []byte {
if lineEnding == "\r\n" {
content = strings.ReplaceAll(content, "\n", "\r\n")
}
return []byte(content)
}
func isText(sample []byte) bool {
const max = 1024
if len(sample) > max {
sample = sample[:max]
}
for i, c := range string(sample) {
if i+utf8.UTFMax > len(sample) {
break
}
if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' && c != '\r' {
return false
}
}
return true
}
File diff suppressed because it is too large. Load diff
+77 -10
View File
@@ -19,6 +19,8 @@ package filesystem
import (
"context"
"fmt"
"strconv"
"time"
"github.com/bishopfox/sliver/client/console"
@@ -28,14 +30,77 @@ import (
"google.golang.org/protobuf/proto"
)
const chtimesDefaultLayout = "2006-01-02 15:04:05"
type chtimesTimeFormat struct {
name string
parse func(string) (int64, error)
}
func chtimesParseUnixSeconds(value string) (int64, error) {
parsed, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return 0, err
}
return parsed, nil
}
func chtimesParseUnixMillis(value string) (int64, error) {
parsed, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return 0, err
}
return time.UnixMilli(parsed).Unix(), nil
}
func chtimesParseLayout(layout string) func(string) (int64, error) {
return func(value string) (int64, error) {
parsed, err := time.Parse(layout, value)
if err != nil {
return 0, err
}
return parsed.Unix(), nil
}
}
func chtimesFormatFromFlags(cmd *cobra.Command) (chtimesTimeFormat, error) {
formatFlags := []struct {
flag string
format chtimesTimeFormat
}{
{flag: "unix", format: chtimesTimeFormat{name: "unix", parse: chtimesParseUnixSeconds}},
{flag: "unix-ms", format: chtimesTimeFormat{name: "unix-ms", parse: chtimesParseUnixMillis}},
{flag: "rfc3339", format: chtimesTimeFormat{name: "rfc3339", parse: chtimesParseLayout(time.RFC3339)}},
{flag: "rfc1123", format: chtimesTimeFormat{name: "rfc1123", parse: chtimesParseLayout(time.RFC1123)}},
}
var selected *chtimesTimeFormat
selectedFlag := ""
for _, candidate := range formatFlags {
enabled, _ := cmd.Flags().GetBool(candidate.flag)
if !enabled {
continue
}
if selected != nil {
return chtimesTimeFormat{}, fmt.Errorf("only one time format flag can be used (--%s and --%s are both set)", selectedFlag, candidate.flag)
}
selected = &candidate.format
selectedFlag = candidate.flag
}
if selected == nil {
return chtimesTimeFormat{name: "datetime", parse: chtimesParseLayout(chtimesDefaultLayout)}, nil
}
return *selected, nil
}
// ChtimesCmd - Change the access and modified time of a file on the remote file system.
func ChtimesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
// DateTime layout (https://pkg.go.dev/time)
layout := "2006-01-02 15:04:05"
filePath := args[0]
if filePath == "" {
@@ -43,33 +108,35 @@ func ChtimesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
return
}
atime := args[1]
format, err := chtimesFormatFromFlags(cmd)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
atime := args[1]
if atime == "" {
con.PrintErrorf("Missing parameter: Last accessed time id\n")
return
}
t_a, err := time.Parse(layout, atime)
unixAtime, err := format.parse(atime)
if err != nil {
con.PrintErrorf("%s\n", err)
con.PrintErrorf("Invalid access time (%s): %s\n", format.name, err)
return
}
unixAtime := t_a.Unix()
mtime := args[2]
if mtime == "" {
con.PrintErrorf("Missing parameter: Last modified time id\n")
return
}
t_b, err := time.Parse(layout, mtime)
unixMtime, err := format.parse(mtime)
if err != nil {
con.PrintErrorf("%s\n", err)
con.PrintErrorf("Invalid modified time (%s): %s\n", format.name, err)
return
}
unixMtime := t_b.Unix()
chtimes, err := con.Rpc.Chtimes(context.Background(), &sliverpb.ChtimesReq{
Request: con.ActiveTarget.Request(cmd),
+33
View File
@@ -56,6 +56,8 @@ var (
consts.CdStr: cdHelp,
consts.PwdStr: pwdHelp,
consts.CatStr: catHelp,
consts.EditStr: editHelp,
consts.HexEditStr: hexEditHelp,
consts.DownloadStr: downloadHelp,
consts.GrepStr: grepHelp,
consts.HeadStr: headHelp,
@@ -63,6 +65,7 @@ var (
consts.UploadStr: uploadHelp,
consts.MkdirStr: mkdirHelp,
consts.RmStr: rmHelp,
consts.ChtimesStr: chtimesHelp,
consts.ProcdumpStr: procdumpHelp,
consts.ElevateStr: elevateHelp,
consts.RunAsStr: runAsHelp,
@@ -298,9 +301,39 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.
rmHelp = `[[.Bold]]Command:[[.Normal]] rm [remote path]
[[.Bold]]About:[[.Normal]] Delete a remote file or directory.`
chtimesHelp = `[[.Bold]]Command:[[.Normal]] chtimes [--unix | --unix-ms | --rfc3339 | --rfc1123] <path> <atime> <mtime>
[[.Bold]]About:[[.Normal]] Change access and modification times on a file (timestomp).
[[.Bold]][[.Underline]]Time Formats[[.Normal]]
Default (datetime): "2006-01-02 15:04:05"
--unix: 1704067200
--unix-ms: 1704067200000
--rfc3339: 2024-01-01T00:00:00Z
--rfc1123: Mon, 02 Jan 2006 15:04:05 MST
[[.Bold]][[.Underline]]Examples[[.Normal]]
chtimes /tmp/file "2024-01-01 12:34:56" "2024-01-01 12:35:56"
chtimes --unix /tmp/file 1704112496 1704112556
chtimes --unix-ms /tmp/file 1704112496000 1704112556000
chtimes --rfc3339 /tmp/file 2024-01-01T12:34:56Z 2024-01-01T12:35:56Z
chtimes --rfc1123 /tmp/file "Mon, 02 Jan 2006 15:04:05 MST" "Mon, 02 Jan 2006 16:04:05 MST"
`
catHelp = `[[.Bold]]Command:[[.Normal]] cat <remote path>
[[.Bold]]About:[[.Normal]] Cat a remote file to stdout.`
editHelp = `[[.Bold]]Command:[[.Normal]] edit <remote path>
[[.Bold]]About:[[.Normal]] Download a remote text file, edit it in a built-in TUI editor, and optionally upload changes.
[[.Bold]]Keys:[[.Normal]] Normal mode uses vim-like navigation (h/j/k/l) and insert mode (i). Use :wq to save+quit or :q to quit.
[[.Bold]]Syntax:[[.Normal]] Use --syntax <lexer> to force a lexer or --syntax-select to pick one interactively.
[[.Bold]]Line Numbers:[[.Normal]] Use --line-numbers to start with line numbers on; toggle with :n.`
hexEditHelp = `[[.Bold]]Command:[[.Normal]] hex-edit [--max-size <size>] [--offset <byte offset>] <remote path>
[[.Bold]]About:[[.Normal]] Download a remote file and edit it in a built-in hex editor, then optionally upload changes.
[[.Bold]]Keys:[[.Normal]] Normal mode uses vim-like navigation (h/j/k/l). Use i for hex edit mode, a for ASCII edit mode, :wq to save+quit or :q to quit.
[[.Bold]]Size Limit:[[.Normal]] Default max size is 8MB. Use --max-size to override (e.g., 512KB, 12MB).
[[.Bold]]Offset:[[.Normal]] Use --offset to jump to a specific byte offset when opening the file.`
downloadHelp = `[[.Bold]]Command:[[.Normal]] download [remote src] <local dst>
[[.Bold]]About:[[.Normal]] Download a file or directory from the remote system. Directories will be downloaded as a gzipped TAR file.
[[.Bold]][[.Underline]]Filters[[.Normal]]
+37
View File
@@ -0,0 +1,37 @@
package hexedit
import (
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// Commands returns the hex-edit command.
func Commands(con *console.SliverClient) []*cobra.Command {
hexEditCmd := &cobra.Command{
Use: consts.HexEditStr + " <remote path>",
Short: "Hex edit a remote file",
Long: help.GetHelpFor([]string{consts.HexEditStr}),
Args: cobra.ExactArgs(1),
GroupID: consts.FilesystemHelpGroup,
Run: func(cmd *cobra.Command, args []string) {
HexEditCmd(cmd, con, args)
},
}
flags.Bind("", false, hexEditCmd, func(f *pflag.FlagSet) {
f.String("max-size", "8MB", "maximum file size to load (e.g. 8MB, 512KB)")
f.Int64("offset", 0, "byte offset to jump to (decimal or 0x...)")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
carapace.Gen(hexEditCmd).PositionalCompletion(
carapace.ActionValues().Usage("path to the remote file to hex edit"),
)
return []*cobra.Command{hexEditCmd}
}
+713
View File
@@ -0,0 +1,713 @@
package hexedit
import (
"fmt"
"strconv"
"strings"
"github.com/bishopfox/sliver/util"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"golang.org/x/term"
)
type editorMode int
const (
modeNormal editorMode = iota
modeHexEdit
modeASCIIEdit
modeCommand
)
type exitAction int
const (
actionNone exitAction = iota
actionQuit
actionSaveQuit
)
const bytesPerLine = 16
type editorResult struct {
Data []byte
Modified bool
Action exitAction
Force bool
}
type editorModel struct {
data []byte
original []byte
modified []bool
modifiedCount int
cursor int
top int
width int
height int
mode editorMode
command string
commandPrefix rune
pending rune
dirty bool
forceQuit bool
action exitAction
filename string
message string
clearMessage bool
nibbleSet bool
nibbleValue byte
}
var (
cursorStyle = lipgloss.NewStyle().Reverse(true)
modifiedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("215"))
modifiedCursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("215")).Reverse(true)
statusStyle = lipgloss.NewStyle().Reverse(true)
lineStyle = lipgloss.NewStyle()
)
func newEditorModel(data []byte, filename string, offset int) *editorModel {
copyData := append([]byte(nil), data...)
model := &editorModel{
data: copyData,
original: append([]byte(nil), data...),
modified: make([]bool, len(copyData)),
mode: modeNormal,
filename: filename,
commandPrefix: ':',
}
if offset > 0 && offset < len(copyData) {
model.cursor = offset
}
return model
}
func (m *editorModel) Init() tea.Cmd {
if m.width == 0 || m.height == 0 {
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
m.width = w
m.height = h
} else {
m.width = 80
m.height = 24
}
m.ensureCursorVisible()
}
return nil
}
func (m *editorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
m.ensureCursorVisible()
return m, nil
case tea.KeyMsg:
if m.clearMessage {
m.message = ""
m.clearMessage = false
}
if msg.Type == tea.KeyCtrlC {
m.action = actionQuit
return m, tea.Quit
}
switch m.mode {
case modeCommand:
return m.handleCommand(msg)
case modeHexEdit:
return m.handleHexEdit(msg)
case modeASCIIEdit:
return m.handleASCIIEdit(msg)
default:
return m.handleNormal(msg)
}
}
return m, nil
}
func (m *editorModel) View() string {
if m.width == 0 || m.height == 0 {
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
m.width = w
m.height = h
} else {
m.width = 80
m.height = 24
}
m.ensureCursorVisible()
}
textHeight := m.textHeight()
var b strings.Builder
for i := 0; i < textHeight; i++ {
lineIndex := m.top + i
b.WriteString(m.renderLine(lineIndex))
if i < textHeight-1 {
b.WriteByte('\n')
}
}
if m.height < 2 {
return b.String()
}
b.WriteByte('\n')
b.WriteString(statusStyle.Width(m.width).Render(m.statusLine()))
if m.height < 3 {
return b.String()
}
b.WriteByte('\n')
b.WriteString(lineStyle.Width(m.width).Render(m.commandLine()))
return b.String()
}
func (m *editorModel) result() editorResult {
data := append([]byte(nil), m.data...)
return editorResult{
Data: data,
Modified: m.dirty,
Action: m.action,
Force: m.forceQuit,
}
}
func (m *editorModel) handleCommand(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.Type {
case tea.KeyEsc:
m.mode = modeNormal
m.command = ""
m.commandPrefix = ':'
case tea.KeyEnter:
m.executeCommand(strings.TrimSpace(m.command))
m.command = ""
m.mode = modeNormal
m.commandPrefix = ':'
if m.action != actionNone {
return m, tea.Quit
}
case tea.KeyBackspace:
m.command = popRune(m.command)
case tea.KeyRunes:
m.command += string(msg.Runes)
}
return m, nil
}
func (m *editorModel) handleHexEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.Type {
case tea.KeyEsc:
m.mode = modeNormal
m.nibbleSet = false
case tea.KeyBackspace:
if m.nibbleSet {
m.nibbleSet = false
return m, nil
}
m.moveLeft()
case tea.KeyLeft:
m.moveLeft()
m.nibbleSet = false
case tea.KeyRight:
m.moveRight()
m.nibbleSet = false
case tea.KeyUp:
m.moveUp()
m.nibbleSet = false
case tea.KeyDown:
m.moveDown()
m.nibbleSet = false
case tea.KeyRunes:
for _, r := range msg.Runes {
val, ok := hexNibble(r)
if !ok {
continue
}
if !m.nibbleSet {
m.nibbleValue = val << 4
m.nibbleSet = true
continue
}
m.setByte(m.nibbleValue | val)
m.nibbleSet = false
m.moveRight()
}
}
m.ensureCursorVisible()
return m, nil
}
func (m *editorModel) handleASCIIEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.Type {
case tea.KeyEsc:
m.mode = modeNormal
case tea.KeyLeft:
m.moveLeft()
case tea.KeyRight:
m.moveRight()
case tea.KeyUp:
m.moveUp()
case tea.KeyDown:
m.moveDown()
case tea.KeyRunes:
for _, r := range msg.Runes {
if r < 0 || r > 0xFF {
continue
}
m.setByte(byte(r))
m.moveRight()
}
}
m.ensureCursorVisible()
return m, nil
}
func (m *editorModel) handleNormal(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.Type {
case tea.KeyEsc:
m.pending = 0
case tea.KeyLeft:
m.moveLeft()
case tea.KeyRight:
m.moveRight()
case tea.KeyUp:
m.moveUp()
case tea.KeyDown:
m.moveDown()
case tea.KeyRunes:
for _, r := range msg.Runes {
if m.handleNormalRune(r) {
break
}
}
}
m.ensureCursorVisible()
return m, nil
}
func (m *editorModel) handleNormalRune(r rune) bool {
if m.pending != 0 {
handled := true
switch m.pending {
case 'g':
if r == 'g' {
m.gotoTop()
} else {
handled = false
}
default:
handled = false
}
m.pending = 0
if handled {
return true
}
}
switch r {
case 'h':
m.moveLeft()
case 'j':
m.moveDown()
case 'k':
m.moveUp()
case 'l':
m.moveRight()
case '0':
m.moveLineStart()
case '$':
m.moveLineEnd()
case 'i':
m.mode = modeHexEdit
m.nibbleSet = false
case 'a':
m.mode = modeASCIIEdit
case 'x':
m.setByte(0x00)
case 'g':
m.pending = 'g'
case 'G':
m.gotoBottom()
case ':':
m.mode = modeCommand
m.command = ""
m.commandPrefix = ':'
}
return false
}
func (m *editorModel) executeCommand(cmd string) {
if cmd == "" {
return
}
if isNumericOffset(cmd) {
if value, err := strconv.ParseInt(cmd, 0, 64); err == nil {
m.gotoOffset(int(value))
return
}
}
switch strings.ToLower(cmd) {
case "q":
m.action = actionQuit
case "q!":
m.action = actionQuit
m.forceQuit = true
case "wq", "x":
m.action = actionSaveQuit
case "w":
m.message = "Use :wq to upload and exit"
m.clearMessage = true
default:
m.message = fmt.Sprintf("Unknown command: %s", cmd)
m.clearMessage = true
}
}
func (m *editorModel) renderLine(lineIndex int) string {
totalWidth := m.textWidth()
if totalWidth <= 0 {
return ""
}
if lineIndex >= m.totalLines() {
return lineStyle.Width(totalWidth).Render("~")
}
lineStart := lineIndex * bytesPerLine
hexCells := make([]string, bytesPerLine)
asciiCells := make([]string, bytesPerLine)
for i := 0; i < bytesPerLine; i++ {
idx := lineStart + i
if idx >= len(m.data) {
hexCells[i] = " "
asciiCells[i] = " "
continue
}
byteVal := m.data[idx]
hexRaw := fmt.Sprintf("%02x", byteVal)
if idx == m.cursor && m.mode == modeHexEdit && m.nibbleSet {
hexRaw = string([]byte{hexChar(m.nibbleValue >> 4), '_'})
}
asciiRaw := string(printableByte(byteVal))
isModified := m.isModified(idx)
if idx == m.cursor {
if isModified {
hexCells[i] = modifiedCursorStyle.Render(hexRaw)
asciiCells[i] = modifiedCursorStyle.Render(asciiRaw)
} else {
hexCells[i] = cursorStyle.Render(hexRaw)
asciiCells[i] = cursorStyle.Render(asciiRaw)
}
continue
}
if isModified {
hexCells[i] = modifiedStyle.Render(hexRaw)
asciiCells[i] = modifiedStyle.Render(asciiRaw)
continue
}
hexCells[i] = hexRaw
asciiCells[i] = asciiRaw
}
var b strings.Builder
b.WriteString(fmt.Sprintf("%08x", lineStart))
b.WriteString(": ")
for i := 0; i < bytesPerLine; i++ {
if i == 8 {
b.WriteByte(' ')
}
b.WriteString(hexCells[i])
if i < bytesPerLine-1 {
b.WriteByte(' ')
}
}
b.WriteString(" |")
for i := 0; i < bytesPerLine; i++ {
b.WriteString(asciiCells[i])
}
b.WriteString("|")
return lineStyle.Width(totalWidth).Render(b.String())
}
func (m *editorModel) statusLine() string {
mode := "NORMAL"
switch m.mode {
case modeHexEdit:
mode = "HEX"
case modeASCIIEdit:
mode = "ASCII"
case modeCommand:
mode = "COMMAND"
}
fileName := baseName(m.filename)
if fileName == "" {
fileName = "[No Name]"
}
modified := ""
if m.dirty {
modified = " [+]"
}
cursor := m.cursor
return fmt.Sprintf(" %s %s Off 0x%08x (%d) Size %s%s", mode, fileName, cursor, cursor, util.ByteCountBinary(int64(len(m.data))), modified)
}
func (m *editorModel) commandLine() string {
if m.mode == modeCommand {
return ":" + m.command
}
if m.message != "" {
return m.message
}
switch m.mode {
case modeHexEdit:
return "ESC: normal hex: 0-9/a-f :wq save+quit :q quit"
case modeASCIIEdit:
return "ESC: normal type to edit bytes :wq save+quit :q quit"
default:
return "i: hex edit a: ascii edit h/j/k/l move x zero :wq save+quit :q quit"
}
}
func (m *editorModel) textHeight() int {
if m.height <= 2 {
return 1
}
return m.height - 2
}
func (m *editorModel) textWidth() int {
if m.width <= 0 {
return 1
}
return m.width
}
func (m *editorModel) totalLines() int {
if len(m.data) == 0 {
return 1
}
return (len(m.data) + bytesPerLine - 1) / bytesPerLine
}
func (m *editorModel) ensureCursorVisible() {
if len(m.data) == 0 {
m.cursor = 0
m.top = 0
return
}
if m.cursor < 0 {
m.cursor = 0
}
if m.cursor >= len(m.data) {
m.cursor = len(m.data) - 1
}
line := m.cursor / bytesPerLine
height := m.textHeight()
if line < m.top {
m.top = line
} else if line >= m.top+height {
m.top = line - height + 1
}
if m.top < 0 {
m.top = 0
}
maxTop := m.totalLines() - height
if maxTop < 0 {
maxTop = 0
}
if m.top > maxTop {
m.top = maxTop
}
}
func (m *editorModel) moveLeft() {
if len(m.data) == 0 {
return
}
if m.cursor > 0 {
m.cursor--
}
}
func (m *editorModel) moveRight() {
if len(m.data) == 0 {
return
}
if m.cursor < len(m.data)-1 {
m.cursor++
}
}
func (m *editorModel) moveUp() {
if len(m.data) == 0 {
return
}
if m.cursor >= bytesPerLine {
m.cursor -= bytesPerLine
}
}
func (m *editorModel) moveDown() {
if len(m.data) == 0 {
return
}
if m.cursor+bytesPerLine < len(m.data) {
m.cursor += bytesPerLine
}
}
func (m *editorModel) moveLineStart() {
if len(m.data) == 0 {
return
}
line := m.cursor / bytesPerLine
m.cursor = line * bytesPerLine
}
func (m *editorModel) moveLineEnd() {
if len(m.data) == 0 {
return
}
line := m.cursor / bytesPerLine
end := (line+1)*bytesPerLine - 1
if end >= len(m.data) {
end = len(m.data) - 1
}
m.cursor = end
}
func (m *editorModel) gotoTop() {
if len(m.data) == 0 {
m.cursor = 0
return
}
m.cursor = 0
}
func (m *editorModel) gotoBottom() {
if len(m.data) == 0 {
m.cursor = 0
return
}
m.cursor = len(m.data) - 1
}
func (m *editorModel) gotoOffset(offset int) {
if len(m.data) == 0 {
m.message = "Empty file"
m.clearMessage = true
return
}
if offset < 0 || offset >= len(m.data) {
m.message = fmt.Sprintf("Offset out of range: %d", offset)
m.clearMessage = true
return
}
m.cursor = offset
m.ensureCursorVisible()
}
func (m *editorModel) setByte(value byte) {
if len(m.data) == 0 {
m.message = "Empty file"
m.clearMessage = true
return
}
if m.cursor >= len(m.data) || m.cursor >= len(m.original) {
return
}
wasModified := m.modified[m.cursor]
orig := m.original[m.cursor]
m.data[m.cursor] = value
if value == orig {
if wasModified {
m.modified[m.cursor] = false
if m.modifiedCount > 0 {
m.modifiedCount--
}
}
} else if !wasModified {
m.modified[m.cursor] = true
m.modifiedCount++
}
m.dirty = m.modifiedCount > 0
}
func printableByte(b byte) byte {
if b >= 32 && b <= 126 {
return b
}
return '.'
}
func (m *editorModel) isModified(idx int) bool {
if idx < 0 || idx >= len(m.modified) {
return false
}
return m.modified[idx]
}
func hexChar(n byte) byte {
if n < 10 {
return '0' + n
}
return 'a' + (n - 10)
}
func hexNibble(r rune) (byte, bool) {
switch {
case r >= '0' && r <= '9':
return byte(r - '0'), true
case r >= 'a' && r <= 'f':
return byte(r-'a') + 10, true
case r >= 'A' && r <= 'F':
return byte(r-'A') + 10, true
}
return 0, false
}
func isNumericOffset(value string) bool {
value = strings.TrimSpace(value)
if value == "" {
return false
}
if strings.HasPrefix(strings.ToLower(value), "0x") {
_, err := strconv.ParseInt(value, 0, 64)
return err == nil
}
for _, r := range value {
if r < '0' || r > '9' {
return false
}
}
return true
}
func popRune(value string) string {
runes := []rune(value)
if len(runes) == 0 {
return value
}
return string(runes[:len(runes)-1])
}
func baseName(path string) string {
if path == "" {
return ""
}
clean := strings.ReplaceAll(path, "\\", "/")
if idx := strings.LastIndex(clean, "/"); idx != -1 {
return clean[idx+1:]
}
return path
}
+349
View File
@@ -0,0 +1,349 @@
package hexedit
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"time"
"github.com/bishopfox/sliver/client/command/filesystem"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/bishopfox/sliver/util/encoders"
tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra"
"google.golang.org/protobuf/proto"
)
const (
beaconPollInterval = 1 * time.Second
)
// HexEditCmd downloads a remote file, opens it in a hex editor, and optionally uploads changes.
func HexEditCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
remotePath := strings.TrimSpace(args[0])
if remotePath == "" {
con.PrintErrorf("Missing parameter: remote path\n")
return
}
maxSizeStr, _ := cmd.Flags().GetString("max-size")
maxSize, err := parseByteSize(maxSizeStr)
if err != nil {
con.PrintErrorf("Invalid --max-size %q: %s\n", maxSizeStr, err)
return
}
if maxSize <= 0 {
con.PrintErrorf("--max-size must be greater than zero\n")
return
}
offset, _ := cmd.Flags().GetInt64("offset")
if offset < 0 {
con.PrintErrorf("--offset must be zero or positive\n")
return
}
fetchSize := maxSize + 1
if fetchSize < maxSize {
fetchSize = maxSize
}
ctx, cancel := con.GrpcContext(cmd)
defer cancel()
ctrl := make(chan bool)
con.SpinUntil(fmt.Sprintf("Downloading %s ...", remotePath), ctrl)
download, err := downloadRemote(ctx, con, cmd, remotePath, fetchSize)
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if download.Response != nil && download.Response.Err != "" {
con.PrintErrorf("%s\n", download.Response.Err)
return
}
if !download.Exists {
con.PrintErrorf("Remote file does not exist: %s\n", remotePath)
return
}
if download.IsDir {
con.PrintErrorf("Remote path is a directory: %s\n", download.Path)
return
}
data := download.Data
if download.Encoder == "gzip" {
data, err = new(encoders.Gzip).Decode(download.Data)
if err != nil {
con.PrintErrorf("Decoding failed %s\n", err)
return
}
}
if int64(len(data)) > maxSize {
con.PrintErrorf("Remote file size %s exceeds --max-size %s\n", util.ByteCountBinary(int64(len(data))), util.ByteCountBinary(maxSize))
return
}
if offset > 0 {
if len(data) == 0 {
con.PrintErrorf("Offset %d out of range for empty file\n", offset)
return
}
if offset >= int64(len(data)) {
con.PrintErrorf("Offset %d out of range (file size %d bytes)\n", offset, len(data))
return
}
}
path := download.Path
if path == "" {
path = remotePath
}
for {
result, err := runHexEditor(data, path, int(offset))
if err != nil {
con.PrintErrorf("Editor error: %s\n", err)
return
}
data = result.Data
if result.Action == actionNone {
return
}
if !result.Modified {
if result.Action == actionSaveQuit {
con.PrintInfof("No changes to upload for %s\n", path)
}
return
}
switch result.Action {
case actionSaveQuit:
uploadNow, err := confirm("Upload changes to remote file (overwrite)?")
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if !uploadNow {
keepEditing, err := confirm("Continue editing?")
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if keepEditing {
continue
}
return
}
if err := uploadEditedContent(cmd, con, path, data); err != nil {
con.PrintErrorf("%s\n", err)
}
return
case actionQuit:
if result.Force {
return
}
discard, err := confirm("Discard changes?")
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if discard {
return
}
}
}
}
func runHexEditor(data []byte, filename string, offset int) (editorResult, error) {
model := newEditorModel(data, filename, offset)
program := tea.NewProgram(model, tea.WithAltScreen())
finalModel, err := program.Run()
if err != nil {
return editorResult{}, err
}
editor, ok := finalModel.(*editorModel)
if !ok {
return editorResult{}, fmt.Errorf("unexpected editor state")
}
return editor.result(), nil
}
func downloadRemote(ctx context.Context, con *console.SliverClient, cmd *cobra.Command, path string, maxBytes int64) (*sliverpb.Download, error) {
download, err := con.Rpc.Download(ctx, &sliverpb.DownloadReq{
Request: con.ActiveTarget.Request(cmd),
Path: path,
RestrictedToFile: true,
MaxBytes: maxBytes,
})
if err != nil {
return nil, err
}
if download.Response != nil && download.Response.Async {
taskID := download.Response.TaskID
download = &sliverpb.Download{}
if err := waitForBeaconTaskResponse(ctx, con, taskID, download); err != nil {
return nil, err
}
}
return download, nil
}
func uploadEditedContent(cmd *cobra.Command, con *console.SliverClient, path string, data []byte) error {
ctx, cancel := con.GrpcContext(cmd)
defer cancel()
uploadData, err := new(encoders.Gzip).Encode(data)
if err != nil {
return err
}
ctrl := make(chan bool)
con.SpinUntil(fmt.Sprintf("Uploading %s ...", path), ctrl)
upload, err := con.Rpc.Upload(ctx, &sliverpb.UploadReq{
Request: con.ActiveTarget.Request(cmd),
Path: path,
Data: uploadData,
Encoder: "gzip",
IsDirectory: false,
Overwrite: true,
})
if err == nil && upload.Response != nil && upload.Response.Async {
taskID := upload.Response.TaskID
upload = &sliverpb.Upload{}
err = waitForBeaconTaskResponse(ctx, con, taskID, upload)
}
ctrl <- true
<-ctrl
if err != nil {
return err
}
filesystem.PrintUpload(upload, con)
return nil
}
func waitForBeaconTaskResponse(ctx context.Context, con *console.SliverClient, taskID string, out proto.Message) error {
task, err := waitForBeaconTask(ctx, con, taskID)
if err != nil {
return err
}
state := strings.ToLower(task.State)
if state != "completed" {
if state == "" {
state = "unknown"
}
return fmt.Errorf("beacon task %s %s", task.ID, state)
}
if len(task.Response) == 0 {
return fmt.Errorf("beacon task %s returned empty response", task.ID)
}
return proto.Unmarshal(task.Response, out)
}
func waitForBeaconTask(ctx context.Context, con *console.SliverClient, taskID string) (*clientpb.BeaconTask, error) {
ticker := time.NewTicker(beaconPollInterval)
defer ticker.Stop()
for {
task, err := con.Rpc.GetBeaconTaskContent(ctx, &clientpb.BeaconTask{ID: taskID})
if err != nil {
return nil, err
}
state := strings.ToLower(task.State)
if state == "completed" || state == "failed" || state == "canceled" {
return task, nil
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-ticker.C:
}
}
}
func confirm(title string) (bool, error) {
answer := false
if err := forms.Confirm(title, &answer); err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return false, nil
}
return false, err
}
return answer, nil
}
func parseByteSize(input string) (int64, error) {
value := strings.TrimSpace(input)
if value == "" {
return 0, fmt.Errorf("size is required")
}
value = strings.ReplaceAll(value, " ", "")
value = strings.ToUpper(value)
idx := 0
for idx < len(value) {
c := value[idx]
if (c >= '0' && c <= '9') || c == '.' {
idx++
continue
}
break
}
if idx == 0 {
return 0, fmt.Errorf("invalid size %q", input)
}
numStr := value[:idx]
unitStr := strings.TrimSpace(value[idx:])
if unitStr == "" {
unitStr = "B"
}
multiplier := int64(1)
switch unitStr {
case "B":
multiplier = 1
case "K", "KB", "KIB":
multiplier = 1024
case "M", "MB", "MIB":
multiplier = 1024 * 1024
case "G", "GB", "GIB":
multiplier = 1024 * 1024 * 1024
case "T", "TB", "TIB":
multiplier = 1024 * 1024 * 1024 * 1024
default:
return 0, fmt.Errorf("unknown size unit %q", unitStr)
}
numVal, err := strconv.ParseFloat(numStr, 64)
if err != nil {
return 0, fmt.Errorf("invalid size %q", input)
}
if numVal <= 0 {
return 0, fmt.Errorf("size must be greater than zero")
}
size := int64(numVal * float64(multiplier))
if size <= 0 {
return 0, fmt.Errorf("size is out of range")
}
return size, nil
}
+7
View File
@@ -1,6 +1,7 @@
package mcp
import (
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
slivermcp "github.com/bishopfox/sliver/client/mcp"
"github.com/spf13/cobra"
@@ -26,6 +27,12 @@ func McpStartCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
ServerVersion: version,
}.WithDefaults()
msg := `Do you know what prompt injection is and are you an adult?`
if !settings.IsUserAnAdultWithPrompt(con, msg) {
con.PrintErrorf("Failed to start MCP server, the user is not qualified to use feature\n")
return
}
if err := slivermcp.Start(cfg, con.Rpc); err != nil {
con.PrintErrorf("%s\n", err)
return
+6 -2
View File
@@ -150,11 +150,15 @@ func Commands(con *console.SliverClient) []*cobra.Command {
}
flags.Bind("", false, chtimesCmd, func(f *pflag.FlagSet) {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
f.Bool("unix", false, "interpret timestamps as Unix seconds")
f.Bool("unix-ms", false, "interpret timestamps as Unix milliseconds")
f.Bool("rfc3339", false, "interpret timestamps as RFC3339")
f.Bool("rfc1123", false, "interpret timestamps as RFC1123")
})
carapace.Gen(chtimesCmd).PositionalCompletion(
carapace.ActionValues().Usage("path to file to change access timestamps"),
carapace.ActionValues().Usage("last accessed time in DateTime format, i.e. 2006-01-02 15:04:05"),
carapace.ActionValues().Usage("last modified time in DateTime format, i.e. 2006-01-02 15:04:05"),
carapace.ActionValues().Usage("last accessed time (default: 2006-01-02 15:04:05; see --help for format flags)"),
carapace.ActionValues().Usage("last modified time (default: 2006-01-02 15:04:05; see --help for format flags)"),
)
getprivsCmd := &cobra.Command{
+6 -1
View File
@@ -41,11 +41,16 @@ func SettingsAutoAdultCmd(cmd *cobra.Command, con *console.SliverClient, args []
// IsUserAnAdult - This should be called for any dangerous (OPSEC-wise) functions.
func IsUserAnAdult(con *console.SliverClient) bool {
return IsUserAnAdultWithPrompt(con, "This action is bad OPSEC, are you an adult?")
}
// IsUserAnAdultWithPrompt prompts with a custom message before continuing.
func IsUserAnAdultWithPrompt(con *console.SliverClient, prompt string) bool {
if GetAutoAdult(con) {
return true
}
confirm := false
_ = forms.Confirm("This action is bad OPSEC, are you an adult?", &confirm)
_ = forms.Confirm(prompt, &confirm)
return confirm
}
+5 -1
View File
@@ -27,6 +27,7 @@ import (
"github.com/bishopfox/sliver/client/command/backdoor"
"github.com/bishopfox/sliver/client/command/cursed"
"github.com/bishopfox/sliver/client/command/dllhijack"
"github.com/bishopfox/sliver/client/command/edit"
"github.com/bishopfox/sliver/client/command/environment"
"github.com/bishopfox/sliver/client/command/exec"
"github.com/bishopfox/sliver/client/command/extensions"
@@ -48,6 +49,7 @@ import (
"github.com/bishopfox/sliver/client/command/tasks"
"github.com/bishopfox/sliver/client/command/wasm"
"github.com/bishopfox/sliver/client/command/wireguard"
"github.com/bishopfox/sliver/client/command/hexedit"
client "github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/reeflective/console"
@@ -79,7 +81,7 @@ func SliverCommands(con *client.SliverClient) console.Commands {
// use.Commands,
tasks.Commands,
pivots.Commands,
aka.ImplantCommands,
aka.ImplantCommands,
)
// [ Info ]
@@ -94,6 +96,8 @@ func SliverCommands(con *client.SliverClient) console.Commands {
// [ Filesystem ]
bind(consts.FilesystemHelpGroup,
edit.Commands,
hexedit.Commands,
filesystem.Commands,
)
+2 -1
View File
@@ -25,8 +25,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
flags.Bind("update", false, updateCmd, func(f *pflag.FlagSet) {
f.BoolP("prereleases", "P", false, "include pre-released (unstable) versions")
f.StringP("proxy", "p", "", "specify a proxy url (e.g. http://localhost:8080)")
f.StringP("save", "s", "", "save downloaded files to specific directory (default user home dir)")
f.StringP("save", "s", "", "save downloaded files to specific directory (default downloads dir)")
f.BoolP("insecure", "I", false, "skip tls certificate validation")
f.Bool("force", false, "skip version comparison and download the latest release")
f.IntP("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
flags.BindFlagCompletions(updateCmd, func(comp *carapace.ActionMap) {
+408 -36
View File
@@ -21,6 +21,7 @@ package update
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
@@ -40,8 +41,19 @@ import (
"github.com/bishopfox/sliver/client/version"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/util"
"github.com/bishopfox/sliver/util/minisign"
"github.com/cheggaaa/pb/v3"
"github.com/spf13/cobra"
"golang.org/x/term"
)
var (
SliverPublicKey string
)
const (
downloadAttempts = 3
downloadBackoff = 750 * time.Millisecond
)
// UpdateCmd - Check for updates.
@@ -94,7 +106,13 @@ func UpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.Printf("\nChecking for updates ... ")
prereleases, _ := cmd.Flags().GetBool("prereleases")
release, err := version.CheckForUpdates(client, prereleases)
force, _ := cmd.Flags().GetBool("force")
var release *version.Release
if force {
release, err = version.LatestRelease(client, prereleases)
} else {
release, err = version.CheckForUpdates(client, prereleases)
}
con.Printf("done!\n\n")
if err != nil {
con.PrintErrorf("Update check failed %s\n", err)
@@ -107,9 +125,13 @@ func UpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.PrintErrorf("%s\n", err)
return
}
updateAvailable(con, client, release, saveTo)
updateAvailable(con, client, release, saveTo, force)
} else {
con.PrintInfof("No new releases.\n")
if force {
con.PrintInfof("No releases found.\n")
} else {
con.PrintInfof("No new releases.\n")
}
}
now := time.Now()
lastCheck := fmt.Appendf(nil, "%d", now.Unix())
@@ -191,84 +213,434 @@ func findAssetFor(prefix string, suffixes []string, assets []version.Asset) *ver
}
func serverAssetForGOOS(assets []version.Asset) *version.Asset {
suffixes := []string{fmt.Sprintf("_%s.zip", runtime.GOOS), runtime.GOOS}
if runtime.GOOS == "darwin" {
suffixes = []string{"_macos.zip", "_macos"}
if runtime.GOARCH == "arm64" {
suffixes = []string{"_macos-arm64.zip", "_macos-arm64"}
}
}
suffixes := assetSuffixes(runtime.GOOS, runtime.GOARCH)
prefix := "sliver-server"
return findAssetFor(prefix, suffixes, assets)
}
func clientAssetForGOOS(assets []version.Asset) *version.Asset {
suffixes := []string{fmt.Sprintf("_%s.zip", runtime.GOOS), runtime.GOOS}
if runtime.GOOS == "darwin" {
suffixes = []string{"_macos.zip", "_macos"}
if runtime.GOARCH == "arm64" {
suffixes = []string{"_macos-arm64.zip", "_macos-arm64"}
}
}
suffixes := assetSuffixes(runtime.GOOS, runtime.GOARCH)
prefix := "sliver-client"
return findAssetFor(prefix, suffixes, assets)
}
func updateAvailable(con *console.SliverClient, client *http.Client, release *version.Release, saveTo string) {
func assetSuffixes(goos, goarch string) []string {
suffixes := make([]string, 0, 8)
seen := make(map[string]struct{}, 8)
add := func(suffix string) {
if suffix == "" {
return
}
if _, ok := seen[suffix]; ok {
return
}
seen[suffix] = struct{}{}
suffixes = append(suffixes, suffix)
}
add(fmt.Sprintf("_%s-%s.zip", goos, goarch))
add(fmt.Sprintf("_%s-%s", goos, goarch))
add(fmt.Sprintf("_%s.zip", goos))
add(fmt.Sprintf("_%s", goos))
if goos == "darwin" {
add(fmt.Sprintf("_macos-%s.zip", goarch))
add(fmt.Sprintf("_macos-%s", goarch))
add("_macos.zip")
add("_macos")
}
return suffixes
}
func updateAvailable(con *console.SliverClient, client *http.Client, release *version.Release, saveTo string, force bool) {
serverAsset := serverAssetForGOOS(release.Assets)
clientAsset := clientAssetForGOOS(release.Assets)
con.Printf("New version available %s\n", release.TagName)
msg := "New version available"
if force {
msg = "Latest release available"
}
con.Printf("%s %s\n", msg, release.TagName)
if serverAsset != nil {
con.Printf(" - Server: %s\n", util.ByteCountBinary(int64(serverAsset.Size)))
}
if clientAsset != nil {
con.Printf(" - Client: %s\n", util.ByteCountBinary(int64(clientAsset.Size)))
}
if serverAsset == nil {
con.PrintWarnf("No server asset available for %s/%s\n", runtime.GOOS, runtime.GOARCH)
}
if clientAsset == nil {
con.PrintWarnf("No client asset available for %s/%s\n", runtime.GOOS, runtime.GOARCH)
}
con.Println()
confirm := false
forms.Confirm("Download update?", &confirm)
if confirm {
con.Printf("Please wait ...")
err := downloadAsset(client, serverAsset, saveTo)
if serverAsset == nil && clientAsset == nil {
con.PrintWarnf("No matching assets found for %s/%s\n", runtime.GOOS, runtime.GOARCH)
return
}
publicKey, err := loadSliverPublicKey()
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
err = downloadAsset(client, clientAsset, saveTo)
if err != nil {
con.PrintErrorf("%s\n", err)
return
downloads := []struct {
label string
asset *version.Asset
}{
{label: "server", asset: serverAsset},
{label: "client", asset: clientAsset},
}
for _, item := range downloads {
if item.asset == nil {
continue
}
con.Printf("Downloading %s update from %s ...\n", item.label, item.asset.BrowserDownloadURL)
if err := downloadAssetWithSignature(con, client, item.asset, release.Assets, saveTo, publicKey); err != nil {
con.PrintErrorf("Failed to download %s update: %s\n", item.label, err)
return
}
con.Println(console.Clearln)
}
con.Println(console.Clearln)
con.PrintInfof("Saved updates to: %s\n", saveTo)
}
}
func downloadAsset(client *http.Client, asset *version.Asset, saveTo string) error {
func downloadAssetWithSignature(con *console.SliverClient, client *http.Client, asset *version.Asset, assets []version.Asset, saveTo string, publicKey minisign.PublicKey) error {
if asset == nil {
return errors.New("no asset available for this platform")
}
downloadURL, err := url.Parse(asset.BrowserDownloadURL)
if err != nil {
return err
}
assetFileName := filepath.Base(downloadURL.Path)
finalPath := filepath.Join(saveTo, assetFileName)
limit := int64(asset.Size)
writer, err := os.Create(filepath.Join(saveTo, assetFileName))
sigURL := asset.BrowserDownloadURL + ".minisig"
var sigLimit int64
if sigAsset := signatureAssetFor(assetFileName, assets); sigAsset != nil && sigAsset.BrowserDownloadURL != "" {
sigURL = sigAsset.BrowserDownloadURL
sigLimit = int64(sigAsset.Size)
}
assetTempPath, err := downloadWithRetries(client, asset.BrowserDownloadURL, saveTo, int64(asset.Size), "asset")
if err != nil {
return fmt.Errorf("download asset: %w", err)
}
sigData, err := downloadBytesWithRetries(client, sigURL, sigLimit, "signature")
if err != nil {
cleanupAssetFiles(assetTempPath, finalPath)
return fmt.Errorf("download signature: %w", err)
}
if err := verifyMinisignSignature(assetTempPath, sigData, assetFileName, publicKey); err != nil {
cleanupAssetFiles(assetTempPath, finalPath)
return err
}
defer writer.Close()
con.PrintSuccessf("Signature verified for %s\n", finalPath)
resp, err := client.Get(asset.BrowserDownloadURL)
if err != nil {
if err := replaceFile(assetTempPath, finalPath); err != nil {
os.Remove(assetTempPath)
return err
}
return nil
}
func signatureAssetFor(assetFileName string, assets []version.Asset) *version.Asset {
want := assetFileName + ".minisig"
for _, asset := range assets {
if asset.Name == want {
return &asset
}
}
return nil
}
func loadSliverPublicKey() (minisign.PublicKey, error) {
if strings.TrimSpace(SliverPublicKey) == "" {
return minisign.PublicKey{}, errors.New("minisign public key not set at build time")
}
var publicKey minisign.PublicKey
if err := publicKey.UnmarshalText([]byte(SliverPublicKey)); err != nil {
return minisign.PublicKey{}, fmt.Errorf("invalid minisign public key: %w", err)
}
return publicKey, nil
}
type httpStatusError struct {
url string
status int
}
func (e httpStatusError) Error() string {
return fmt.Sprintf("unexpected HTTP status %d for %s", e.status, e.url)
}
func downloadWithRetries(client *http.Client, downloadURL, saveTo string, limit int64, label string) (string, error) {
var lastErr error
backoff := downloadBackoff
for attempt := 1; attempt <= downloadAttempts; attempt++ {
path, err := downloadOnce(client, downloadURL, saveTo, limit, label)
if err == nil {
return path, nil
}
lastErr = err
if !isRetryableDownloadError(err) || attempt == downloadAttempts {
break
}
time.Sleep(backoff)
backoff *= 2
}
return "", lastErr
}
func downloadBytesWithRetries(client *http.Client, downloadURL string, limit int64, label string) ([]byte, error) {
var lastErr error
backoff := downloadBackoff
for attempt := 1; attempt <= downloadAttempts; attempt++ {
data, err := downloadBytesOnce(client, downloadURL, limit, label)
if err == nil {
return data, nil
}
lastErr = err
if !isRetryableDownloadError(err) || attempt == downloadAttempts {
break
}
time.Sleep(backoff)
backoff *= 2
}
return nil, lastErr
}
func downloadOnce(client *http.Client, downloadURL, saveTo string, limit int64, label string) (string, error) {
parsedURL, err := url.Parse(downloadURL)
if err != nil {
return "", err
}
baseName := filepath.Base(parsedURL.Path)
tmpFile, err := os.CreateTemp(saveTo, baseName+".tmp-*")
if err != nil {
return "", err
}
success := false
defer func() {
if !success {
tmpFile.Close()
os.Remove(tmpFile.Name())
}
}()
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, downloadURL, nil)
if err != nil {
return "", err
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
bar := pb.Full.Start64(limit)
barReader := bar.NewProxyReader(resp.Body)
io.Copy(writer, barReader)
bar.Finish()
if resp.StatusCode != http.StatusOK {
return "", httpStatusError{url: downloadURL, status: resp.StatusCode}
}
if limit <= 0 && resp.ContentLength > 0 {
limit = resp.ContentLength
}
var reader io.Reader = resp.Body
var bar *pb.ProgressBar
if limit > 0 {
bar = pb.Full.Start64(limit)
bar.Set("prefix", fmt.Sprintf("%s: ", label))
bar.Set(pb.Bytes, true)
bar.Set(pb.CleanOnFinish, true)
bar.Set(pb.ReturnSymbol, "\r")
if term.IsTerminal(int(os.Stdin.Fd())) {
bar.Set(pb.Terminal, true)
}
reader = bar.NewProxyReader(reader)
}
n, copyErr := io.Copy(tmpFile, reader)
if bar != nil {
bar.Finish()
}
if copyErr != nil {
return "", copyErr
}
if limit > 0 && n != limit {
return "", fmt.Errorf("downloaded %d bytes, expected %d", n, limit)
}
if err := tmpFile.Sync(); err != nil {
return "", err
}
if err := tmpFile.Close(); err != nil {
return "", err
}
success = true
return tmpFile.Name(), nil
}
func downloadBytesOnce(client *http.Client, downloadURL string, limit int64, label string) ([]byte, error) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, downloadURL, nil)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, httpStatusError{url: downloadURL, status: resp.StatusCode}
}
if limit <= 0 && resp.ContentLength > 0 {
limit = resp.ContentLength
}
var reader io.Reader = resp.Body
var bar *pb.ProgressBar
if limit > 0 {
bar = pb.Full.Start64(limit)
bar.Set("prefix", fmt.Sprintf("%s: ", label))
bar.Set(pb.Bytes, true)
bar.Set(pb.CleanOnFinish, true)
bar.Set(pb.ReturnSymbol, "\r")
if term.IsTerminal(int(os.Stdin.Fd())) {
bar.Set(pb.Terminal, true)
}
reader = bar.NewProxyReader(reader)
}
data, readErr := readAllWithLimit(reader, limit)
if bar != nil {
bar.Finish()
}
if readErr != nil {
return nil, readErr
}
return data, nil
}
func isRetryableDownloadError(err error) bool {
if err == nil {
return false
}
if errors.Is(err, io.ErrUnexpectedEOF) {
return true
}
var netErr net.Error
if errors.As(err, &netErr) {
return netErr.Timeout() || netErr.Temporary()
}
var statusErr httpStatusError
if errors.As(err, &statusErr) {
switch statusErr.status {
case http.StatusTooManyRequests, http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:
return true
default:
return false
}
}
return false
}
func verifyMinisignSignature(artifactPath string, sigData []byte, expectedFile string, publicKey minisign.PublicKey) error {
artifact, err := os.Open(artifactPath)
if err != nil {
return fmt.Errorf("open artifact: %w", err)
}
defer artifact.Close()
reader := minisign.NewReader(artifact)
if _, err := io.Copy(io.Discard, reader); err != nil {
return fmt.Errorf("stream artifact: %w", err)
}
if !reader.Verify(publicKey, sigData) {
return errors.New("signature verification failed")
}
if err := verifyTrustedComment(sigData, expectedFile); err != nil {
return err
}
return nil
}
func readAllWithLimit(reader io.Reader, limit int64) ([]byte, error) {
if limit <= 0 {
return io.ReadAll(reader)
}
lr := &io.LimitedReader{R: reader, N: limit + 1}
data, err := io.ReadAll(lr)
if err != nil {
return nil, err
}
if int64(len(data)) > limit {
return nil, fmt.Errorf("downloaded %d bytes, expected %d", len(data), limit)
}
if int64(len(data)) != limit {
return nil, fmt.Errorf("downloaded %d bytes, expected %d", len(data), limit)
}
return data, nil
}
func verifyTrustedComment(signature []byte, expectedFile string) error {
var sig minisign.Signature
if err := sig.UnmarshalText(signature); err != nil {
return fmt.Errorf("parse signature: %w", err)
}
fileField := trustedCommentFile(sig.TrustedComment)
if fileField == "" {
return fmt.Errorf("trusted comment missing file field: %q", sig.TrustedComment)
}
if fileField != expectedFile {
return fmt.Errorf("trusted comment file mismatch: expected %q, got %q", expectedFile, fileField)
}
return nil
}
func trustedCommentFile(trustedComment string) string {
for _, field := range strings.Fields(trustedComment) {
if after, ok := strings.CutPrefix(field, "file:"); ok {
return after
}
}
return ""
}
func replaceFile(src, dst string) error {
if err := os.Remove(dst); err != nil && !os.IsNotExist(err) {
return err
}
return os.Rename(src, dst)
}
func cleanupAssetFiles(tempPath, finalPath string) {
if tempPath != "" {
_ = os.Remove(tempPath)
}
if finalPath != "" {
_ = os.Remove(finalPath)
}
}
@@ -22,10 +22,12 @@ import (
"context"
"errors"
"io"
"mime"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
@@ -117,7 +119,7 @@ func webAddFile(web *clientpb.WebsiteAddContent, webpath string, contentType str
}
if contentType == "" {
contentType = sniffContentType(file)
contentType = sniffContentType(file, contentPath)
}
web.Contents[webpath] = &clientpb.WebContent{
@@ -135,13 +137,19 @@ func confirmAddDirectory() bool {
return confirm
}
func sniffContentType(out *os.File) string {
func sniffContentType(out *os.File, contentPath string) string {
ext := strings.ToLower(filepath.Ext(contentPath))
if ext != "" {
contentType := mime.TypeByExtension(ext)
if contentType != "" {
return contentType
}
}
out.Seek(0, io.SeekStart)
buffer := make([]byte, fileSampleSize)
_, err := out.Read(buffer)
if err != nil {
return defaultMimeType
}
contentType := http.DetectContentType(buffer)
return contentType
return http.DetectContentType(buffer)
}
+9
View File
@@ -227,6 +227,15 @@ func (con *SliverClient) PrintSuccessf(format string, args ...any) {
con.printf(Clearln+Success+format, args...)
}
// PrintSuccess prints a success message with a default or provided message.
func (con *SliverClient) PrintSuccess(args ...any) {
if len(args) == 0 {
con.PrintSuccessf("Success")
return
}
con.PrintSuccessf("%s", fmt.Sprint(args...))
}
// PrintWarnf a warning message immediately below the last line of output.
func (con *SliverClient) PrintWarnf(format string, args ...any) {
logger := slog.New(con.jsonHandler)
+2
View File
@@ -220,6 +220,8 @@ const (
CdStr = "cd"
PwdStr = "pwd"
CatStr = "cat"
EditStr = "edit"
HexEditStr = "hex-edit"
DownloadStr = "download"
HeadStr = "head"
TailStr = "tail"
+8
View File
@@ -31,6 +31,7 @@ import (
"github.com/bishopfox/sliver/protobuf/rpcpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util/leaky"
"golang.org/x/time/rate"
)
var (
@@ -210,6 +211,8 @@ const leakyBufSize = 4108 // data.len(2) + hmacsha1(10) + data(4096)
var leakyBuf = leaky.NewLeakyBuf(2048, leakyBufSize)
func connect(conn net.Conn, stream rpcpb.SliverRPC_SocksProxyClient, frame *sliverpb.SocksData) {
// Client Rate Limiter: 10 operations per second, burst of 1
limiter := rate.NewLimiter(rate.Limit(10), 1)
SocksConnPool.Store(frame.TunnelID, conn)
@@ -241,6 +244,11 @@ func connect(conn net.Conn, stream rpcpb.SliverRPC_SocksProxyClient, frame *sliv
return
}
if n > 0 {
if err := limiter.Wait(context.Background()); err != nil {
log.Printf("[socks] rate limiter error: %s", err)
return
}
frame.Data = buff[:n]
frame.Sequence = ToImplantSequence
log.Printf("[socks] (User to Client) to Server to agent Data Sequence %d , Data Size %d \n", ToImplantSequence, len(frame.Data))
+65 -20
View File
@@ -63,6 +63,68 @@ func (r *Release) Published() (time.Time, error) {
// occurs we don't really try to recover. If client is nil we just use the Go
// default client with default settings.
func CheckForUpdates(client *http.Client, prereleases bool) (*Release, error) {
releases, err := fetchReleases(client)
if err != nil {
return nil, err
}
if len(releases) == 0 {
return nil, nil
}
current := canonicalSemver(Version)
if current == "" {
current = "v0.0.0"
}
for _, release := range releases {
if release == nil {
continue
}
if release.Prerelease && !prereleases {
continue
}
releaseVersion := canonicalSemver(release.TagName)
if releaseVersion == "" {
continue
}
if semver.Compare(releaseVersion, current) > 0 {
return release, nil
}
}
return nil, nil
}
// LatestRelease - Returns the latest release without comparing to the current version.
func LatestRelease(client *http.Client, prereleases bool) (*Release, error) {
releases, err := fetchReleases(client)
if err != nil {
return nil, err
}
if len(releases) == 0 {
return nil, nil
}
var latest *Release
var latestVersion string
for _, release := range releases {
if release == nil {
continue
}
if release.Prerelease && !prereleases {
continue
}
releaseVersion := canonicalSemver(release.TagName)
if releaseVersion == "" {
continue
}
if latest == nil || semver.Compare(releaseVersion, latestVersion) > 0 {
latest = release
latestVersion = releaseVersion
}
}
return latest, nil
}
func fetchReleases(client *http.Client) ([]*Release, error) {
skip := os.Getenv(skipCheckEnv)
if skip != "" || GithubReleasesURL == "" {
return nil, nil
@@ -84,27 +146,10 @@ func CheckForUpdates(client *http.Client, prereleases bool) (*Release, error) {
if resp.StatusCode != 200 {
return nil, errors.New("API returned non-200 status code")
}
releases := &[]*Release{}
err = json.Unmarshal(body, releases)
if err != nil {
releases := []*Release{}
if err := json.Unmarshal(body, &releases); err != nil {
return nil, err
}
current := canonicalSemver(Version)
if current == "" {
current = "v0.0.0"
}
for _, release := range *releases {
if release.Prerelease && !prereleases {
continue
}
releaseVersion := canonicalSemver(release.TagName)
if releaseVersion == "" {
continue
}
if semver.Compare(releaseVersion, current) > 0 {
return release, nil
}
}
return nil, nil
return releases, nil
}
+396 -406
View File
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,607 @@
### Notifications
#### Table of Contents
- [Overview](/docs?name=Notifications#overview)
- [Configuration Overview](/docs?name=Notifications#configuration-overview)
- [Global Examples](/docs?name=Notifications#global-examples)
- [Service Examples](/docs?name=Notifications#service-examples)
- [Amazon SES](/docs?name=Notifications#amazon-ses)
- [Amazon SNS](/docs?name=Notifications#amazon-sns)
- [Bark](/docs?name=Notifications#bark)
- [DingTalk (DingDing)](/docs?name=Notifications#dingtalk-dingding)
- [Discord](/docs?name=Notifications#discord)
- [FCM](/docs?name=Notifications#fcm)
- [Google Chat](/docs?name=Notifications#google-chat)
- [HTTP Webhooks](/docs?name=Notifications#http-webhooks)
- [Lark](/docs?name=Notifications#lark)
- [Line](/docs?name=Notifications#line)
- [Line Notify](/docs?name=Notifications#line-notify)
- [Mail (SMTP)](/docs?name=Notifications#mail-smtp)
- [Mailgun](/docs?name=Notifications#mailgun)
- [Matrix](/docs?name=Notifications#matrix)
- [Mattermost](/docs?name=Notifications#mattermost)
- [Microsoft Teams](/docs?name=Notifications#microsoft-teams)
- [PagerDuty](/docs?name=Notifications#pagerduty)
- [Plivo](/docs?name=Notifications#plivo)
- [Pushbullet](/docs?name=Notifications#pushbullet)
- [Pushbullet SMS](/docs?name=Notifications#pushbullet-sms)
- [Pushover](/docs?name=Notifications#pushover)
- [Reddit](/docs?name=Notifications#reddit)
- [Rocket.Chat](/docs?name=Notifications#rocketchat)
- [SendGrid](/docs?name=Notifications#sendgrid)
- [Slack](/docs?name=Notifications#slack)
- [Syslog](/docs?name=Notifications#syslog)
- [Telegram](/docs?name=Notifications#telegram)
- [TextMagic](/docs?name=Notifications#textmagic)
- [Twilio](/docs?name=Notifications#twilio)
- [Twitter](/docs?name=Notifications#twitter)
- [Viber](/docs?name=Notifications#viber)
- [Web Push](/docs?name=Notifications#web-push)
- [WeChat](/docs?name=Notifications#wechat)
- [WhatsApp](/docs?name=Notifications#whatsapp)
- [Custom Templates](/docs?name=Notifications#custom-templates)
- [Notes](/docs?name=Notifications#notes)
#### Overview
Sliver can send server event notifications via the `github.com/nikoksr/notify` services. Notifications are configured in `server.yaml` under the `notifications` block (located at `~/.sliver/configs/server.yaml` by default).
Notifications are disabled by default. Enable them globally, then enable one or more services. You can optionally filter which events are sent globally or per-service.
#### Configuration Overview
- `notifications.enabled` - Master switch for notifications.
- `notifications.events` - Optional list of event types to send. Empty means all events.
- `notifications.services.<service>.enabled` - Enable a specific service.
- `notifications.services.<service>.events` - Optional per-service event filter. If empty, the global list is used.
- Any string field can reference an environment variable by using `$ENV_VAR` or `${ENV_VAR}`.
#### Global Examples
##### Example: Slack + Telegram with Event Filters
```yaml
notifications:
enabled: true
events:
- session-connected
- session-disconnected
services:
slack:
enabled: true
api_token: "$SLIVER_SLACK_TOKEN"
channels:
- "C0123456789"
telegram:
enabled: true
api_token: "$SLIVER_TELEGRAM_TOKEN"
chat_ids:
- "123456789"
events:
- session-connected
```
##### Example: HTTP Webhooks + Syslog
```yaml
notifications:
enabled: true
services:
http:
enabled: true
urls:
- "https://hooks.example.com/sliver"
webhooks:
- url: "https://hooks.example.com/sliver-auth"
method: "POST"
content_type: "application/json"
headers:
Authorization: "$SLIVER_WEBHOOK_TOKEN"
syslog:
enabled: true
priority: "info"
network: "udp"
address: "127.0.0.1:514"
tag: "sliver"
```
#### Service Examples
Each example below shows the service-specific block under `notifications.services`. All examples assume `notifications.enabled: true`.
##### Amazon SES
```yaml
services:
amazon_ses:
enabled: true
access_key_id: "$AWS_ACCESS_KEY_ID"
secret_key: "$AWS_SECRET_ACCESS_KEY"
region: "us-east-1"
sender_address: "[email protected]"
receivers:
- "[email protected]"
```
##### Amazon SNS
```yaml
services:
amazon_sns:
enabled: true
access_key_id: "$AWS_ACCESS_KEY_ID"
secret_key: "$AWS_SECRET_ACCESS_KEY"
region: "us-east-1"
receivers:
- "https://sns.us-east-1.amazonaws.com/123456789012/my-topic"
```
##### Bark
```yaml
services:
bark:
enabled: true
device_key: "$BARK_DEVICE_KEY"
servers:
- "https://api.day.app/"
```
##### DingTalk (DingDing)
```yaml
services:
dingding:
enabled: true
token: "$DINGDING_TOKEN"
secret: "$DINGDING_SECRET"
```
##### Discord
```yaml
services:
discord:
enabled: true
token: "$DISCORD_BOT_TOKEN"
token_type: "bot" # bot (default) or oauth
channels:
- "123456789012345678"
```
##### FCM
```yaml
services:
fcm:
enabled: true
credentials_file: "/path/to/firebase.json"
project_id: "my-firebase-project"
device_tokens:
- "$FCM_DEVICE_TOKEN"
```
##### Google Chat
```yaml
services:
google_chat:
enabled: true
credentials_file: "/path/to/google-chat.json"
spaces:
- "AAAA1234567"
```
##### HTTP Webhooks
```yaml
services:
http:
enabled: true
urls:
- "https://hooks.example.com/sliver"
webhooks:
- url: "https://hooks.example.com/sliver-auth"
method: "POST"
content_type: "application/json"
headers:
Authorization: "$SLIVER_WEBHOOK_TOKEN"
```
##### Lark
```yaml
services:
lark:
enabled: true
webhook:
url: "$LARK_WEBHOOK_URL"
custom_app:
app_id: "$LARK_APP_ID"
app_secret: "$LARK_APP_SECRET"
receivers:
- type: "open_id"
id: "ou_1234567890abcdef"
```
##### Line
```yaml
services:
line:
enabled: true
channel_secret: "$LINE_CHANNEL_SECRET"
channel_access_token: "$LINE_CHANNEL_ACCESS_TOKEN"
receivers:
- "U1234567890abcdef"
```
##### Line Notify
```yaml
services:
line_notify:
enabled: true
receivers:
- "$LINE_NOTIFY_TOKEN"
```
##### Mail (SMTP)
```yaml
services:
mail:
enabled: true
sender_address: "[email protected]"
smtp_host: "smtp.example.com:587"
smtp_identity: ""
smtp_username: "$SMTP_USER"
smtp_password: "$SMTP_PASSWORD"
smtp_auth_host: "smtp.example.com"
body_type: "plain" # plain or html
receivers:
- "[email protected]"
```
##### Mailgun
```yaml
services:
mailgun:
enabled: true
domain: "mg.example.com"
api_key: "$MAILGUN_API_KEY"
sender_address: "[email protected]"
receivers:
- "[email protected]"
```
##### Matrix
```yaml
services:
matrix:
enabled: true
user_id: "@bot:matrix.example"
room_id: "!roomid:matrix.example"
home_server: "https://matrix.example"
access_token: "$MATRIX_ACCESS_TOKEN"
```
##### Mattermost
```yaml
services:
mattermost:
enabled: true
url: "https://mattermost.example.com"
login_id: "$MM_LOGIN_ID"
password: "$MM_PASSWORD"
channels:
- "channel-id"
```
##### Microsoft Teams
```yaml
services:
msteams:
enabled: true
webhooks:
- "$MSTEAMS_WEBHOOK"
wrap_text: true
disable_webhook_validation: false
user_agent: "sliver-notify"
```
##### PagerDuty
```yaml
services:
pagerduty:
enabled: true
token: "$PAGERDUTY_TOKEN"
from_address: "[email protected]"
receivers:
- "P123456"
notification_type: "incident"
urgency: "high"
priority_id: "PQ123456"
```
##### Plivo
```yaml
services:
plivo:
enabled: true
auth_id: "$PLIVO_AUTH_ID"
auth_token: "$PLIVO_AUTH_TOKEN"
source: "+15551234567"
callback_url: "https://example.com/plivo"
callback_method: "POST"
receivers:
- "+15557654321"
```
##### Pushbullet
```yaml
services:
pushbullet:
enabled: true
api_token: "$PUSHBULLET_TOKEN"
device_nicknames:
- "MyPhone"
```
##### Pushbullet SMS
```yaml
services:
pushbullet_sms:
enabled: true
api_token: "$PUSHBULLET_TOKEN"
device_nickname: "MyPhone"
phone_numbers:
- "+15557654321"
```
##### Pushover
```yaml
services:
pushover:
enabled: true
app_token: "$PUSHOVER_APP_TOKEN"
recipients:
- "$PUSHOVER_USER_KEY"
```
##### Reddit
```yaml
services:
reddit:
enabled: true
client_id: "$REDDIT_CLIENT_ID"
client_secret: "$REDDIT_CLIENT_SECRET"
username: "$REDDIT_USERNAME"
password: "$REDDIT_PASSWORD"
recipients:
- "target_user"
```
##### Rocket.Chat
```yaml
services:
rocketchat:
enabled: true
server_url: "chat.example.com"
scheme: "https"
user_id: "$ROCKETCHAT_USER_ID"
token: "$ROCKETCHAT_TOKEN"
channels:
- "#ops"
```
##### SendGrid
```yaml
services:
sendgrid:
enabled: true
api_key: "$SENDGRID_API_KEY"
sender_address: "[email protected]"
sender_name: "Sliver"
receivers:
- "[email protected]"
```
##### Slack
```yaml
services:
slack:
enabled: true
api_token: "$SLIVER_SLACK_TOKEN"
channels:
- "C0123456789"
```
##### Syslog
```yaml
services:
syslog:
enabled: true
priority: "info"
network: "udp"
address: "127.0.0.1:514"
tag: "sliver"
```
##### Telegram
```yaml
services:
telegram:
enabled: true
api_token: "$SLIVER_TELEGRAM_TOKEN"
chat_ids:
- "123456789"
parse_mode: "HTML"
```
##### TextMagic
```yaml
services:
textmagic:
enabled: true
username: "$TEXTMAGIC_USER"
api_key: "$TEXTMAGIC_API_KEY"
phone_numbers:
- "+15557654321"
```
##### Twilio
```yaml
services:
twilio:
enabled: true
account_sid: "$TWILIO_ACCOUNT_SID"
auth_token: "$TWILIO_AUTH_TOKEN"
from_number: "+15551234567"
phone_numbers:
- "+15557654321"
```
##### Twitter
```yaml
services:
twitter:
enabled: true
consumer_key: "$TWITTER_CONSUMER_KEY"
consumer_secret: "$TWITTER_CONSUMER_SECRET"
access_token: "$TWITTER_ACCESS_TOKEN"
access_token_secret: "$TWITTER_ACCESS_TOKEN_SECRET"
recipients:
- "1234567890"
```
##### Viber
```yaml
services:
viber:
enabled: true
app_key: "$VIBER_APP_KEY"
sender_name: "Sliver"
sender_avatar: "https://example.com/avatar.png"
webhook_url: "https://example.com/viber-webhook"
receivers:
- "viber-user-id"
```
##### Web Push
```yaml
services:
webpush:
enabled: true
vapid_public_key: "$WEBPUSH_PUBLIC_KEY"
vapid_private_key: "$WEBPUSH_PRIVATE_KEY"
subscriptions:
- endpoint: "https://push.example.com/123"
keys:
p256dh: "$WEBPUSH_P256DH"
auth: "$WEBPUSH_AUTH"
```
##### WeChat
```yaml
services:
wechat:
enabled: true
app_id: "$WECHAT_APP_ID"
app_secret: "$WECHAT_APP_SECRET"
token: "$WECHAT_TOKEN"
encoding_aes_key: "$WECHAT_AES_KEY"
receivers:
- "wechat-user-id"
```
##### WhatsApp
```yaml
services:
whatsapp:
enabled: true
receivers:
- "whatsapp-contact"
```
#### Custom Templates
You can render notification messages using Go templates with either `template/text` or `template/html`. Templates are loaded from:
```
<app dir>/notifications/templates/
```
By default this is `~/.sliver/notifications/templates/`. Only a filename is allowed (no path separators) and `..` is rejected to prevent path traversal.
Configure per-event templates under `notifications.templates`:
```yaml
notifications:
enabled: true
templates:
session-connected:
type: "text" # text (default) or html
template: "session.tmpl"
session-disconnected:
type: "html"
template: "session.html"
```
Template data available:
- `.EventType`
- `.Session`
- `.Beacon`
- `.Job`
- `.Client`
- `.Error`
- `.DefaultSubject`
- `.DefaultMessage`
- `.Timestamp` (UTC)
Example `session.tmpl` (text):
```txt
{{ .DefaultSubject }}
Event: {{ .EventType }}
Host: {{ if .Session }}{{ .Session.Hostname }}{{ end }}
User: {{ if .Session }}{{ .Session.Username }}{{ end }}
```
Example `session.html` (html):
```html
<h3>{{ .DefaultSubject }}</h3>
<p>Event: {{ .EventType }}</p>
<p>Host: {{ if .Session }}{{ .Session.Hostname }}{{ end }}</p>
<p>User: {{ if .Session }}{{ .Session.Username }}{{ end }}</p>
```
#### Notes
- Environment variable expansion only applies to string fields (API keys, tokens, URLs, etc.).
- If a service configuration is incomplete, that service is skipped and the server continues without it.
@@ -1,5 +1,3 @@
# This course is intended for the 1.6 version of Sliver, which is not yet published
`sliver-server` is the binary you want to use to run the Sliver C2 server, `sliver-client` is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you don’t necessarily run sliver server and client separately.
First time running Sliver will take a couple seconds as it's retrieving its dependencies. Consecutive executions will be much faster. Go ahead and launch the `sliver-server`.
+1 -1
View File
@@ -1 +1 @@
{"tutorials":[{"name":"1 - Getting Started","content":"# This course is intended for the 1.6 version of Sliver, which is not yet published\n\n`sliver-server` is the binary you want to use to run the Sliver C2 server, `sliver-client` is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you don’t necessarily run sliver server and client separately.\n\nFirst time running Sliver will take a couple seconds as it's retrieving its dependencies. Consecutive executions will be much faster. Go ahead and launch the `sliver-server`.\n\n```asciinema\n{\"src\": \"/asciinema/startup.cast\", \"cols\": \"132\", \"rows\": \"28\", \"idleTimeLimit\": 8}\n```\n\nLet's take a couple minutes to discuss what Sliver actually is and how it's set up.\n\n![Alt text](/images/Architecture.png)\n\nNow that Sliver is running, lets generate and execute your first implant to try out some of the basic features of Sliver, for now we’re going to run everything on the local host.\n\nHere's what we're going to do: \n* Generate your implant using the `generate` command as shown below.\n* Start HTTP listener on port 80\n* Execute implant in a separate terminal\n\n```asciinema\n{\"src\": \"/asciinema/first-implant.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNow let’s select our implant and run our first command using the `use` command.\n\n```bash\n[server] sliver > use\n? Select a session or beacon: \nSESSION 1884a365 RELATED_EARDRUM [::1]:49153 test.local tester darwin/amd64\n[*] Active session RELATED_EARDRUM (1884a365-085f-4506-b28e-80c481730fd0)\n\n[server] sliver (RELATED_EARDRUM) > pwd\n\n[*] /Users/tester/tools\n```\n\nOnce you have reached this point, go ahead and explore some of the commands listed below. In each case, first check out the command's help using the **`-h`** flag then try it out!\n\n```bash\nExploring and interacting with the filesystem\n\nFilesystem\n cat Dump file to stdout\n cd Change directory\n cp Copy a file\n download Download a file\n grep Search for strings that match a regex within a file or directory\n head Grab the first number of bytes or lines from a file\n ls List current directory\n mkdir Make a directory\n mv Move or rename a file\n pwd Print working directory\n rm Remove a file or directory\n tail Grab the last number of bytes or lines from a file\n upload Upload a file\n```\n\n```asciinema\n{\"src\": \"/asciinema/filesystem.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nGetting some environmental information\n```bash\nInfo\n env List environment variables\n getgid Get session process GID\n getpid Get session pid\n getuid Get session process UID\n info Get session info\n ping Send round trip message to implant (does not use ICMP)\n whoami Get session user execution context\n```\nExecute a binary\n\n```asciinema\n{\"src\": \"/asciinema/execute.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nRunning an interactive shell\n\n```asciinema\n{\"src\": \"/asciinema/shell.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNot all commands are supported for all operating systems or meant to be used on all operating systems, here are a few examples:\n\n```\nmount Get information on mounted filesystems (only for Windows and Linux)\nscreenshot Take a screenshot (only for Windows and Linux)\nmemfiles List current memfiles (Linux only)\n```\n\nThis is an open-source project if you see a bug please file an issue! Pull requests are always welcome."},{"name":"2 - Beacons vs Sessions","content":"Sliver implants support two types of connections, sessions and beacons.\n\nSessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.\n\nTypically during an engagement you will want to deploy a beacon on the target system and switch to a session while doing more active enumeration.\n\nLet’s start with generating and deploying a beacon using `http`.\n\n```asciinema\n{\"src\": \"/asciinema/beacon_generation.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nYou can see the beacon callback times either in the `info` command or using `beacons watch`.\n\n```bash\n[server] sliver > beacons watch\n\n ID Name Transport Username Operating System Last Check-Line truncated
{"tutorials":[{"name":"1 - Getting Started","content":"`sliver-server` is the binary you want to use to run the Sliver C2 server, `sliver-client` is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you don’t necessarily run sliver server and client separately.\n\nFirst time running Sliver will take a couple seconds as it's retrieving its dependencies. Consecutive executions will be much faster. Go ahead and launch the `sliver-server`.\n\n```asciinema\n{\"src\": \"/asciinema/startup.cast\", \"cols\": \"132\", \"rows\": \"28\", \"idleTimeLimit\": 8}\n```\n\nLet's take a couple minutes to discuss what Sliver actually is and how it's set up.\n\n![Alt text](/images/Architecture.png)\n\nNow that Sliver is running, lets generate and execute your first implant to try out some of the basic features of Sliver, for now we’re going to run everything on the local host.\n\nHere's what we're going to do: \n* Generate your implant using the `generate` command as shown below.\n* Start HTTP listener on port 80\n* Execute implant in a separate terminal\n\n```asciinema\n{\"src\": \"/asciinema/first-implant.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNow let’s select our implant and run our first command using the `use` command.\n\n```bash\n[server] sliver > use\n? Select a session or beacon: \nSESSION 1884a365 RELATED_EARDRUM [::1]:49153 test.local tester darwin/amd64\n[*] Active session RELATED_EARDRUM (1884a365-085f-4506-b28e-80c481730fd0)\n\n[server] sliver (RELATED_EARDRUM) > pwd\n\n[*] /Users/tester/tools\n```\n\nOnce you have reached this point, go ahead and explore some of the commands listed below. In each case, first check out the command's help using the **`-h`** flag then try it out!\n\n```bash\nExploring and interacting with the filesystem\n\nFilesystem\n cat Dump file to stdout\n cd Change directory\n cp Copy a file\n download Download a file\n grep Search for strings that match a regex within a file or directory\n head Grab the first number of bytes or lines from a file\n ls List current directory\n mkdir Make a directory\n mv Move or rename a file\n pwd Print working directory\n rm Remove a file or directory\n tail Grab the last number of bytes or lines from a file\n upload Upload a file\n```\n\n```asciinema\n{\"src\": \"/asciinema/filesystem.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nGetting some environmental information\n```bash\nInfo\n env List environment variables\n getgid Get session process GID\n getpid Get session pid\n getuid Get session process UID\n info Get session info\n ping Send round trip message to implant (does not use ICMP)\n whoami Get session user execution context\n```\nExecute a binary\n\n```asciinema\n{\"src\": \"/asciinema/execute.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nRunning an interactive shell\n\n```asciinema\n{\"src\": \"/asciinema/shell.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNot all commands are supported for all operating systems or meant to be used on all operating systems, here are a few examples:\n\n```\nmount Get information on mounted filesystems (only for Windows and Linux)\nscreenshot Take a screenshot (only for Windows and Linux)\nmemfiles List current memfiles (Linux only)\n```\n\nThis is an open-source project if you see a bug please file an issue! Pull requests are always welcome."},{"name":"2 - Beacons vs Sessions","content":"Sliver implants support two types of connections, sessions and beacons.\n\nSessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.\n\nTypically during an engagement you will want to deploy a beacon on the target system and switch to a session while doing more active enumeration.\n\nLet’s start with generating and deploying a beacon using `http`.\n\n```asciinema\n{\"src\": \"/asciinema/beacon_generation.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nYou can see the beacon callback times either in the `info` command or using `beacons watch`.\n\n```bash\n[server] sliver > beacons watch\n\n ID Name Transport Username Operating System Last Check-In Next Check-In\n========== =============== =========== ================= ==========Line truncated
+131 -14
View File
@@ -4,7 +4,9 @@ go 1.25.5
replace github.com/rsteube/carapace v0.36.3 => github.com/reeflective/carapace v0.46.3-0.20231214181515-27e49f3c3b69
replace github.com/reeflective/readline => github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d
replace github.com/reeflective/readline => github.com/moloch--/readline v0.0.0-20260129035512-cb5b3e87d51a
replace github.com/reeflective/console => github.com/moloch--/console v0.0.0-20260129035459-883dcb25c701
require (
filippo.io/age v1.3.1
@@ -13,6 +15,7 @@ require (
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0
github.com/Ne0nd0g/go-clr v1.0.3
github.com/SherClockHolmes/webpush-go v1.4.0
github.com/alecthomas/chroma v0.10.0
github.com/charmbracelet/huh v0.8.0
github.com/charmbracelet/huh/spinner v0.0.0-20251215014908-6f7d32faaff3
@@ -36,8 +39,9 @@ require (
github.com/miekg/dns v1.1.70
github.com/moloch--/asciicast v0.1.1
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e
github.com/moloch--/sgn v0.0.4
github.com/moloch--/sgn v0.0.5
github.com/ncruces/go-sqlite3 v0.30.4
github.com/nikoksr/notify v1.5.0
github.com/reeflective/console v0.1.25
github.com/reeflective/readline v1.1.4
github.com/rsteube/carapace v0.50.2
@@ -51,7 +55,7 @@ require (
github.com/xlab/treeprint v1.2.0
github.com/yiya1989/sshkrb5 v0.0.1
golang.org/x/crypto v0.46.0
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9
golang.org/x/exp v0.0.0-20251209150349-8475f28825e9
golang.org/x/mod v0.31.0
golang.org/x/net v0.48.0
golang.org/x/sys v0.40.0
@@ -59,32 +63,145 @@ require (
golang.org/x/text v0.32.0
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
google.golang.org/grpc v1.76.0
google.golang.org/api v0.257.0
google.golang.org/grpc v1.77.0
google.golang.org/protobuf v1.36.11
gorm.io/driver/mysql v1.6.0
gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.31.0
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c
maunium.net/go/mautrix v0.26.0
modernc.org/sqlite v1.39.0
tailscale.com v1.92.5
)
require (
cel.dev/expr v0.25.1 // indirect
cloud.google.com/go v0.123.0 // indirect
cloud.google.com/go/auth v0.17.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/firestore v1.20.0 // indirect
cloud.google.com/go/iam v1.5.3 // indirect
cloud.google.com/go/longrunning v0.7.0 // indirect
cloud.google.com/go/monitoring v1.24.3 // indirect
cloud.google.com/go/storage v1.58.0 // indirect
filippo.io/hpke v0.4.0 // indirect
firebase.google.com/go/v4 v4.18.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect
github.com/MicahParks/keyfunc v1.9.0 // indirect
github.com/PagerDuty/go-pagerduty v1.8.0 // indirect
github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20250718055228-285ecf400b48 // indirect
github.com/alecthomas/chroma/v2 v2.23.1 // indirect
github.com/appleboy/go-fcm v1.2.6 // indirect
github.com/atc0005/go-teams-notify/v2 v2.14.0 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.5 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
github.com/aws/aws-sdk-go-v2/service/ses v1.34.17 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sns v1.39.10 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
github.com/aws/smithy-go v1.24.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/blinkbean/dingtalk v1.1.3 // indirect
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/bwmarrin/discordgo v0.29.0 // indirect
github.com/caarlos0/go-reddit/v3 v3.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
github.com/creachadair/msync v0.7.1 // indirect
github.com/cschomburg/go-pushbullet v0.0.0-20171206132031-67759df45fbb // indirect
github.com/dghubble/oauth1 v0.7.3 // indirect
github.com/dghubble/sling v1.4.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-lark/lark v1.16.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/gregdel/pushover v1.4.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/go-types v0.0.0-20240719050749-165e75e768f7 // indirect
github.com/kevinburke/rest v0.0.0-20250718180114-1a15e4f2364f // indirect
github.com/kevinburke/twilio-go v0.0.0-20250718182727-5fe7adc01f29 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/line/line-bot-sdk-go v7.8.0+incompatible // indirect
github.com/mailgun/errors v0.4.0 // indirect
github.com/mailgun/mailgun-go/v5 v5.8.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mileusna/viber v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/oapi-codegen/runtime v1.1.2 // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/plivo/plivo-go/v7 v7.59.2 // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect
github.com/silenceper/wechat/v2 v2.1.11 // indirect
github.com/slack-go/slack v0.17.3 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/textmagic/textmagic-rest-go-v2/v3 v3.0.43885 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect
github.com/ttacon/libphonenumber v1.2.1 // indirect
github.com/utahta/go-linenotify v0.5.0 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.mau.fi/util v0.9.3 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect
go.opentelemetry.io/otel v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect
go.opentelemetry.io/otel/trace v1.39.0 // indirect
google.golang.org/appengine/v2 v2.0.6 // indirect
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
)
require (
cloud.google.com/go/compute/metadata v0.7.0 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
@@ -95,11 +212,11 @@ require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 // indirect
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 // indirect
github.com/aws/aws-sdk-go-v2 v1.39.2 // indirect
github.com/aws/aws-sdk-go-v2/config v1.31.12 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.0 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/carapace-sh/carapace v1.11.0 // indirect
github.com/carapace-sh/carapace v1.11.0
github.com/carapace-sh/carapace-shlex v1.1.1 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
@@ -111,7 +228,7 @@ require (
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/chromedp/sysutil v1.1.0 // indirect
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/coder/websocket v1.8.12 // indirect
github.com/coder/websocket v1.8.14 // indirect
github.com/coreos/go-iptables v0.8.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dblohm7/wingoes v0.0.0-20250822163801-6d8e6105c62d // indirect
@@ -182,19 +299,19 @@ require (
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/time v0.13.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.40.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 // indirect
gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gopkg.in/yaml.v3 v3.0.1
modernc.org/libc v1.66.10 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
+463 -66
View File
@@ -2,9 +2,31 @@
9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f/go.mod h1:hHyrZRryGqVdqrknjq5OWDLGCTJ2NeEvtrpR96mjraM=
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd h1:ZLsPO6WdZ5zatV4UfVpr7oAwLGRZ+sebTUruuM4Ra3M=
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd/go.mod h1:SrHC2C7r5GkDk8R+NFVzYy/sdj0Ypg9htaPXQq5Cqeo=
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/firestore v1.20.0 h1:JLlT12QP0fM2SJirKVyu2spBCO8leElaW0OOtPm6HEo=
cloud.google.com/go/firestore v1.20.0/go.mod h1:jqu4yKdBmDN5srneWzx3HlKrHFWFdlkgjgQ6BKIOFQo=
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY=
cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw=
cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=
cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
cloud.google.com/go/storage v1.58.0 h1:PflFXlmFJjG/nBeR9B7pKddLQWaFaRWx4uUi/LyNxxo=
cloud.google.com/go/storage v1.58.0/go.mod h1:cMWbtM+anpC74gn6qjLh+exqYcfmB9Hqe5z6adx+CLI=
cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=
cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
filippo.io/age v1.3.1 h1:hbzdQOJkuaMEpRCLSN1/C5DX74RPcNCk6oqhKMXmZi0=
filippo.io/age v1.3.1/go.mod h1:EZorDTYUxt836i3zdori5IJX/v2Lj6kWFU0cfh6C0D4=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
@@ -13,6 +35,8 @@ filippo.io/hpke v0.4.0 h1:p575VVQ6ted4pL+it6M00V/f2qTZITO0zgmdKCkd5+A=
filippo.io/hpke v0.4.0/go.mod h1:EmAN849/P3qdeK+PCMkDpDm83vRHM5cDipBJ8xbQLVY=
filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc=
filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA=
firebase.google.com/go/v4 v4.18.0 h1:S+g0P72oDGqOaG4wlLErX3zQmU9plVdu7j+Bc3R1qFw=
firebase.google.com/go/v4 v4.18.0/go.mod h1:P7UfBpzc8+Z3MckX79+zsWzKVfpGryr6HLbAe7gCWfs=
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb h1:c1Bb1n1wr2xgTHkNVxkUHBshff33m+IXgY6lE6oYttg=
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb/go.mod h1:fSQ9asCJK/lIGXUnoRwJNMXcnzel53AsZMCtlVBIn3k=
github.com/Binject/debug v0.0.0-20210225042342-c9b8b45728d2/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
@@ -28,12 +52,30 @@ github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0/go.mod h1:J3XDRl
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo=
github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o=
github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Ne0nd0g/go-clr v1.0.3 h1:xt92wwuqY23ZSC7RuHD3mKu3K22Bk5NNbxI803vojK4=
github.com/Ne0nd0g/go-clr v1.0.3/go.mod h1:TKYSQ/5xT25EvBUttAlUrzpR8yHuI0qTRK495I5xG/I=
github.com/PagerDuty/go-pagerduty v1.8.0 h1:MTFqTffIcAervB83U7Bx6HERzLbyaSPL/+oxH3zyluI=
github.com/PagerDuty/go-pagerduty v1.8.0/go.mod h1:nzIeAqyFSJAFkjWKvMzug0JtwDg+V+UoCWjFrfFH5mI=
github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20250718055228-285ecf400b48 h1:I4Uc+H5JgZ2o/o6AGz2fIQCf/pVio9mQDVffXkDVc24=
github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20250718055228-285ecf400b48/go.mod h1:rjP7sIipbZcagro/6TCk6X0ZeFT2eyudH5+fve/cbBA=
github.com/SherClockHolmes/webpush-go v1.4.0 h1:ocnzNKWN23T9nvHi6IfyrQjkIc0oJWv1B1pULsf9i3s=
github.com/SherClockHolmes/webpush-go v1.4.0/go.mod h1:XSq8pKX11vNV8MJEMwjrlTkxhAj1zKfxmyhdV7Pd6UA=
github.com/VirusTotal/vt-go v0.0.0-20210528074736-45bbe34cc8ab/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE=
github.com/VirusTotal/vt-go v1.0.1 h1:rj/qugIY8XNC6ogwOaeJAGCOsb3nmY63+yuDMtHOx0Q=
github.com/VirusTotal/vt-go v1.0.1/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE=
@@ -44,44 +86,60 @@ github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.30.0 h1:uA3uhDbCxfO9+DI/DuGeAMr9qI+noVWwGPNTFuKID5M=
github.com/alicebob/miniredis/v2 v2.30.0/go.mod h1:84TWKZlxYkfgMucPBf5SOQBYJceZeQRFIaQgNMiCX6Q=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/appleboy/go-fcm v1.2.6 h1:TU5/+2QnmTNjWkHLe9hUB9EPJ5Bv+CegzTJI0qK0QgA=
github.com/appleboy/go-fcm v1.2.6/go.mod h1:nvi8DgoMax8o6nwQYgO8pIXSX6iaQY7yDYvtwIGa6aI=
github.com/atc0005/go-teams-notify/v2 v2.14.0 h1:7N+xw+COnYANLREaAveQ65rsNQ12nIZJED9nMLyscCo=
github.com/atc0005/go-teams-notify/v2 v2.14.0/go.mod h1:EECsWM2b0Hvoz7O+QdlsvyN2KCUOFQCGj8bUBXv3A3Q=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 h1:JjwxKkxzcBk4k8147g0eBQRCIy0UN1Be8AAv6RaIj4Q=
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0/go.mod h1:IbERvuyb387Hppp8hX0SQTFt/mkej8+OhuS8L0nC2CI=
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 h1:cIAK2NNf2yafdgpFRNJrgZMwvy61BEVpGoHc2n4/yWs=
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4/go.mod h1:SalMPBCab3yuID8nIhLfzwoBV+lBRyaC7NhuN8qL8xE=
github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I=
github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
github.com/aws/aws-sdk-go-v2/config v1.31.12 h1:pYM1Qgy0dKZLHX2cXslNacbcEFMkDMl+Bcj5ROuS6p8=
github.com/aws/aws-sdk-go-v2/config v1.31.12/go.mod h1:/MM0dyD7KSDPR+39p9ZNVKaHDLb9qnfDurvVS2KAhN8=
github.com/aws/aws-sdk-go-v2/credentials v1.18.16 h1:4JHirI4zp958zC026Sm+V4pSDwW4pwLefKrc0bF2lwI=
github.com/aws/aws-sdk-go-v2/credentials v1.18.16/go.mod h1:qQMtGx9OSw7ty1yLclzLxXCRbrkjWAM7JnObZjmCB7I=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 h1:Mv4Bc0mWmv6oDuSWTKnk+wgeqPL5DRFu5bQL9BGPQ8Y=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9/go.mod h1:IKlKfRppK2a1y0gy1yH6zD+yX5uplJ6UuPlgd48dJiQ=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 h1:se2vOWGD3dWQUtfn4wEjRQJb1HK1XsNIt825gskZ970=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9/go.mod h1:hijCGH2VfbZQxqCDN7bwz/4dzxV+hkyhjawAtdPWKZA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 h1:6RBnKZLkJM4hQ+kN6E7yWFveOTg8NLPHAkqrs4ZPlTU=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9/go.mod h1:V9rQKRmK7AWuEsOMnHzKj8WyrIir1yUJbZxDuZLFvXI=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 h1:5r34CgVOD4WZudeEKZ9/iKpiT6cM1JyEROpXjOcdWv8=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9/go.mod h1:dB12CEbNWPbzO2uC6QSWHteqOg4JfBVJOojbAoAUb5I=
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
github.com/aws/aws-sdk-go-v2/config v1.32.5 h1:pz3duhAfUgnxbtVhIK39PGF/AHYyrzGEyRD9Og0QrE8=
github.com/aws/aws-sdk-go-v2/config v1.32.5/go.mod h1:xmDjzSUs/d0BB7ClzYPAZMmgQdrodNjPPhd6bGASwoE=
github.com/aws/aws-sdk-go-v2/credentials v1.19.5 h1:xMo63RlqP3ZZydpJDMBsH9uJ10hgHYfQFIk1cHDXrR4=
github.com/aws/aws-sdk-go-v2/credentials v1.19.5/go.mod h1:hhbH6oRcou+LpXfA/0vPElh/e0M3aFeOblE1sssAAEk=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
github.com/aws/aws-sdk-go-v2/service/ses v1.34.17 h1:XR7CtY988tck2Bhuy1JP4FsV8z0OAwjuh+gb7nAy8/M=
github.com/aws/aws-sdk-go-v2/service/ses v1.34.17/go.mod h1:2CspeTVldnJdRixX36SzTZuoIpjyKlfeXyB7/JB5KGk=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
github.com/aws/aws-sdk-go-v2/service/sns v1.39.10 h1:wqErrLzV3iERQ7dbZbKQS0gOM6ngxZtmPwKyRGn+Krc=
github.com/aws/aws-sdk-go-v2/service/sns v1.39.10/go.mod h1:OiwBtRz6QlQyt69WLBMvSiyfgI7cOd6xSJ9ThTMjI5M=
github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 h1:TFg6XiS7EsHN0/jpV3eVNczZi/sPIVP5jxIs+euIESQ=
github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1/go.mod h1:OIezd9K0sM/64DDP4kXx/i0NdgXu6R5KE6SCsIPJsjc=
github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 h1:A1oRkiSQOWstGh61y4Wc/yQ04sqrQZr1Si/oAXj20/s=
github.com/aws/aws-sdk-go-v2/service/sso v1.29.6/go.mod h1:5PfYspyCU5Vw1wNPsxi15LZovOnULudOQuVxphSflQA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 h1:5fm5RTONng73/QA73LhCNR7UT9RpFH3hR6HWL6bIgVY=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1/go.mod h1:xBEjWD13h+6nq+z4AkqSfSvqRKFgDIQeaMguAJndOWo=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 h1:p3jIvqYwUZgu/XYeI48bJxOhvm47hZb5HUQ0tn6Q9kA=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.6/go.mod h1:WtKK+ppze5yKPkZ0XwqIVWD4beCwv056ZbPQNoeHqM8=
github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 h1:eYnlt6QxnFINKzwxP5/Ucs1vkG7VT3Iezmvfgc2waUw=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.7/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
@@ -89,19 +147,29 @@ github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/blinkbean/dingtalk v1.1.3 h1:MbidFZYom7DTFHD/YIs+eaI7kRy52kmWE/sy0xjo6E4=
github.com/blinkbean/dingtalk v1.1.3/go.mod h1:9BaLuGSBqY3vT5hstValh48DbsKO7vaHaJnG9pXwbto=
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I=
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/carapace-sh/carapace v1.9.0 h1:IuEP6YeeK4fhrH7MoOyL0I3GMhvROerkw6YGxkx8Fbw=
github.com/carapace-sh/carapace v1.9.0/go.mod h1:Zs3DpsawpFm+8fEfRVYE9EzMBYH8u7S3Aw9yAAKX0JM=
github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno=
github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/caarlos0/go-reddit/v3 v3.0.1 h1:w8ugvsrHhaE/m4ez0BO/sTBOBWI9WZTjG7VTecHnql4=
github.com/caarlos0/go-reddit/v3 v3.0.1/go.mod h1:QlwgmG5SAqxMeQvg/A2dD1x9cIZCO56BMnMdjXLoisI=
github.com/carapace-sh/carapace v1.11.0 h1:dTUFXvIrUTLd9BGLsfDt4wCgEyPl8bJeZOFDmN7fIfo=
github.com/carapace-sh/carapace v1.11.0/go.mod h1:NVkuv1s8wyFvu7d98rdgYwqGKMB6GO1MEUZp5RcWri8=
github.com/carapace-sh/carapace-shlex v1.1.0 h1:58QSJTUxghpa/rEn6+Z9UM/Hy0ipVHDUn9S1mp4i+ZE=
github.com/carapace-sh/carapace-shlex v1.1.0/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
github.com/carapace-sh/carapace-shlex v1.1.1 h1:ccmNeetAYZOk4IcV36youFDsXusT9uCNW2Njkw+QS+Q=
github.com/carapace-sh/carapace-shlex v1.1.1/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws=
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
@@ -140,16 +208,22 @@ github.com/chromedp/chromedp v0.14.1 h1:0uAbnxewy/Q+Bg7oafVePE/6EXEho9hnaC38f+TT
github.com/chromedp/chromedp v0.14.1/go.mod h1:rHzAv60xDE7VNy/MYtTUrYreSc0ujt2O1/C3bzctYBo=
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creachadair/msync v0.7.1 h1:SeZmuEBXQPe5GqV/C94ER7QIZPwtvFbeQiykzt/7uho=
@@ -158,14 +232,23 @@ github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoi
github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/cschomburg/go-pushbullet v0.0.0-20171206132031-67759df45fbb h1:7X9nrm+LNWdxzQOiCjy0G51rNUxbH35IDHCjAMvogyM=
github.com/cschomburg/go-pushbullet v0.0.0-20171206132031-67759df45fbb/go.mod h1:RfQ9wji3fjcSEsQ+uFCtIh3+BXgcZum8Kt3JxvzYzlk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dblohm7/wingoes v0.0.0-20250822163801-6d8e6105c62d h1:QRKpU+9ZBDs62LyBfwhZkJdB5DJX2Sm3p4kUh7l1aA0=
github.com/dblohm7/wingoes v0.0.0-20250822163801-6d8e6105c62d/go.mod h1:SUxUaAK/0UG5lYyZR1L1nC4AaYYvSSYTWQSH3FPcxKU=
github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/demisto/goxforce v0.0.0-20160322194047-db8357535b1d h1:hmOGJg3cq5XK2aMs7R4kXXVSHqHMaC5hI5fwkX7V2zE=
github.com/demisto/goxforce v0.0.0-20160322194047-db8357535b1d/go.mod h1:q72QzdO6OUjwTqnLCFJczIQ7GsBa4ffzkQiQcq6rVTY=
github.com/dghubble/oauth1 v0.7.3 h1:EkEM/zMDMp3zOsX2DC/ZQ2vnEX3ELK0/l9kb+vs4ptE=
github.com/dghubble/oauth1 v0.7.3/go.mod h1:oxTe+az9NSMIucDPDCCtzJGsPhciJV33xocHfcR2sVY=
github.com/dghubble/sling v1.4.2 h1:vs1HIGBbSl2SEALyU+irpYFLZMfc49Fp+jYryFebQjM=
github.com/dghubble/sling v1.4.2/go.mod h1:o0arCOz0HwfqYQJLrRtqunaWOn4X6jxE/6ORKRpVTD4=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
@@ -173,19 +256,37 @@ github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYC
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7 h1:uh1GSejOhVPRQmoXZxY82TiewZB8QXiaP1skL7Nun3Y=
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7/go.mod h1:ncTaGuXc5v7AuiVekeJ0Nwh8Bf4cudukoj0qM/15UZE=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM=
github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329/go.mod h1:Alz8LEClvR7xKsrq3qzoc4N0guvVNSS8KmSChGYr9hs=
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gaissmai/bart v0.25.0 h1:eqiokVPqM3F94vJ0bTHXHtH91S8zkKL+bKh+BsGOsJM=
@@ -198,21 +299,41 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3 h1:02WINGfSX5w0Mn+F28UyRoSt9uvMhKguwWMlOAh6U/0=
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-lark/lark v1.16.0 h1:U6BwkLM9wrZedSM7cIiMofganr8PCvJN+M75w2lf2Gg=
github.com/go-lark/lark v1.16.0/go.mod h1:6ltbSztPZRT6IaO9ZIQyVaY5pVp/KeMizDYtfZkU+vM=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc h1:jZY+lpZB92nvBo2f31oPC/ivGll6NcsnEOORm8Fkr4M=
github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc/go.mod h1:25mL1NKxbJhB63ihiK8MnNeTRd+xAizd6bOdydrTLUQ=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo=
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737/go.mod h1:MIS0jDzbU/vuM9MC4YnBITCv+RYuTRq8dJzmCrFsK9g=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
@@ -221,12 +342,20 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
@@ -234,31 +363,69 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/go-tpm v0.9.4 h1:awZRf9FwOeTunQmHoDYSHJps3ie6f1UlhS1fOdPEt1I=
github.com/google/go-tpm v0.9.4/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/nftables v0.3.0 h1:bkyZ0cbpVeMHXOrtlFc8ISmfVqq5gPJukoYieyVmITg=
github.com/google/nftables v0.3.0/go.mod h1:BCp9FsrbF1Fn/Yu6CLUc9GGZFw/+hsxfluNXXmxBfRM=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v0.0.0-20171113160352-8c31c18f31ed/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 h1:4EZlYQIiyecYJlUbVkFXCXHz1QPhVXcHnQKAzBTPfQo=
github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4/go.mod h1:lEO7XoHJ/xNRBCxrn4h/CEB67h0kW1B0t4ooP2yrjUA=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregdel/pushover v1.4.0 h1:P77WAJ2zPG+b0mEsmMjWGrPMuvhkh9k3v7OviwsoveE=
github.com/gregdel/pushover v1.4.0/go.mod h1:EcaO66Nn1StkpEm1iKtBTV3d2A16SoMsVER1PthX7to=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk=
github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -288,13 +455,25 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90=
github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018 h1:NQYgMY188uWrS+E/7xMVpydsI48PMHcc7SfR4OxkDF4=
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018/go.mod h1:Pmpz2BLf55auQZ67u3rvyI2vAQvNetkK/4zYUmpauZQ=
github.com/kevinburke/go-types v0.0.0-20240719050749-165e75e768f7 h1:36PMhfw/I1YYAjOOuA66ll5X7NJ8v3cJEqsAxiMv7bE=
github.com/kevinburke/go-types v0.0.0-20240719050749-165e75e768f7/go.mod h1:8tQOif9eUJLpDnvfDcGtesfv6VpL2UvDbW4l8kXnSDE=
github.com/kevinburke/rest v0.0.0-20250718180114-1a15e4f2364f h1:y+inhBsY0ewgXFXCXlxodNxkXdYeU9YuneCYQEnRkmw=
github.com/kevinburke/rest v0.0.0-20250718180114-1a15e4f2364f/go.mod h1:3cBF15uOiTj025Ll5QHLw317EB+e06+AEwyt7oHUubI=
github.com/kevinburke/twilio-go v0.0.0-20250718182727-5fe7adc01f29 h1:AZ5D/KUEro9jOlMv94YJoufveb3vwM5WBwQ0Pfsh+XI=
github.com/kevinburke/twilio-go v0.0.0-20250718182727-5fe7adc01f29/go.mod h1:Z7bqFOTtIqCCIO+uPtod7Fp3gPMR8aTCcp5Pe8D+3fE=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
@@ -313,22 +492,34 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lesnuages/go-winio v0.4.19 h1:lFDu1mnhg5em+8zTHO4ChSD11J56xkyE8m3N8IrVmbA=
github.com/lesnuages/go-winio v0.4.19/go.mod h1:rm7jf4kBcldxMeljR7c7XY1qVCBc+8z3PtSgkrT9Clk=
github.com/lesnuages/snitch v0.6.0 h1:vOao32MdYDHYDcTAq2CKszDh6Dn8Bv1Gmgc8uVh193U=
github.com/lesnuages/snitch v0.6.0/go.mod h1:RfXlAVJYL7gUoZ9VMMon4LqrPiAjC8x62Ntbse4a4s0=
github.com/line/line-bot-sdk-go v7.8.0+incompatible h1:Uf9/OxV0zCVfqyvwZPH8CrdiHXXmMRa/L91G3btQblQ=
github.com/line/line-bot-sdk-go v7.8.0+incompatible/go.mod h1:0RjLjJEAU/3GIcHkC3av6O4jInAbt25nnZVmOFUgDBg=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8=
github.com/mailgun/errors v0.4.0/go.mod h1:xGBaaKdEdQT0/FhwvoXv4oBaqqmVZz9P1XEnvD/onc0=
github.com/mailgun/mailgun-go/v5 v5.8.1 h1:QoRa2oBqLq+8GTYgM4id/cKW/WySQRka3PtnW8IK6KQ=
github.com/mailgun/mailgun-go/v5 v5.8.1/go.mod h1:qNTXXuJi9/myqpDLI8Mbn54WCXdto1kEHm6I2/WWYQQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy/I=
github.com/mark3labs/mcp-go v0.43.2/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
@@ -348,20 +539,29 @@ github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/mileusna/viber v1.0.1 h1:gWB6/lKoWYVxkH0Jb8jRnGIRZ/9DEM7RBZRJHRfdYWs=
github.com/mileusna/viber v1.0.1/go.mod h1:Pxu/iPMnYjnHgu+bEp3SiKWHWmlf/kDp/yOX8XUdYrQ=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/moloch--/asciicast v0.1.1 h1:btwwniCK9br3ys33XZ71IG/M3ZujSSCcBzsH/sieiXg=
github.com/moloch--/asciicast v0.1.1/go.mod h1:OckO16UDLgxVLclrCnbocL1ix15Br/8Xv/caBoYq98o=
github.com/moloch--/console v0.0.0-20260129035459-883dcb25c701 h1:6caCgDZpovjvv+eclRytFDf0GpVEnjDmoJRHpfkb42E=
github.com/moloch--/console v0.0.0-20260129035459-883dcb25c701/go.mod h1:UdqyqqCSnPu3Zc8Q3kpDszrq1hcyBHsG17hlWlRJmWI=
github.com/moloch--/go-keystone v0.0.2 h1:xThbYs45CSzoDUz5kBZxAyTPIpK3t5M7NB1x+NTTRL8=
github.com/moloch--/go-keystone v0.0.2/go.mod h1:kpGRJpw9b7Eq3MluHrba+GPKZhSyDrvhLULSwH5nGGM=
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e h1:IkFCPlAa0iTiLxck+NqAwBx8JDlnHYm4orOQBbs4BDQ=
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e/go.mod h1:eYeI6cQ5YHhHt9i0BBW0zc1DaQnb4ZMXsSPuEV/V5Og=
github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d h1:9RbKockJl6ZdgoEh2QqU3An6Ij/NWZtRWS3J6yG0+qo=
github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d/go.mod h1:CwNkh9BmFBBCSO6mdDaNWb34rOqQsI9eYbxyqvOEazY=
github.com/moloch--/sgn v0.0.4 h1:90IC5rx3TmPEfrqPnENB0yyiqcbcNk9LqezXx7uruF0=
github.com/moloch--/sgn v0.0.4/go.mod h1:FyWfCtdynywgLmVR71vTJztZ3WkGSkR91Y4EOijUutE=
github.com/moloch--/readline v0.0.0-20260129035512-cb5b3e87d51a h1:Q69DxLWF+Z8oIjuNbeKC6Xygk8Z4heWzzO79Io1cEYA=
github.com/moloch--/readline v0.0.0-20260129035512-cb5b3e87d51a/go.mod h1:CwNkh9BmFBBCSO6mdDaNWb34rOqQsI9eYbxyqvOEazY=
github.com/moloch--/sgn v0.0.5 h1:92GFaRpe+353uhqzFiNKXKgmTiD4dBxnvb8gd4MaV/g=
github.com/moloch--/sgn v0.0.5/go.mod h1:FyWfCtdynywgLmVR71vTJztZ3WkGSkR91Y4EOijUutE=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
@@ -370,6 +570,7 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/ncruces/go-sqlite3 v0.30.4 h1:j9hEoOL7f9ZoXl8uqXVniaq1VNwlWAXihZbTvhqPPjA=
github.com/ncruces/go-sqlite3 v0.30.4/go.mod h1:7WR20VSC5IZusKhUdiR9y1NsUqnZgqIYCmKKoMEYg68=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
@@ -378,8 +579,29 @@ github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/nikoksr/notify v1.5.0 h1:mzkCw8eb0P+qHwgmGQyPPGqz4GH+07FJDr44Bs16T9k=
github.com/nikoksr/notify v1.5.0/go.mod h1:CEV9Bw9Y59K5oj7d8h83Xl32ATeL43ZEg9qTQsfwcCc=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
@@ -387,10 +609,16 @@ github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/errors v0.8.1-0.20161029093637-248dadf4e906/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/plivo/plivo-go/v7 v7.59.2 h1:S3CGORMXXYOnuKXBQ1Sk9DtSbeXa+bRA4S3TypfpOQY=
github.com/plivo/plivo-go/v7 v7.59.2/go.mod h1:ceCFoYEzQrtrJjLcU7HR/r6Vz2kAVSaylrL7SjGPymc=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -401,14 +629,15 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/reeflective/console v0.1.25 h1:do/r6rcKH6pCQDEzh5PhoVFcqHkqcjd0N6tNjpmYWWQ=
github.com/reeflective/console v0.1.25/go.mod h1:It/IqEjo0PowaBuxBS9pEi5EYUgmtvQd0ytMtlS77kk=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/rsteube/carapace v0.50.2 h1:9nPOBmIKPySFQvtBRGghFafRCR09Cf9K4/BEo1M+VFE=
github.com/rsteube/carapace v0.50.2/go.mod h1:syVOvI8e2rEEK/9aMZxfWuHvcnQK/EcnTV4roClEnLE=
github.com/rsteube/carapace-shlex v0.1.2 h1:ZKjhIfXoCkEnzensMglTaLbkNOaLkmM8SCRshpJKx6s=
@@ -416,28 +645,51 @@ github.com/rsteube/carapace-shlex v0.1.2/go.mod h1:zPw1dOFwvLPKStUy9g2BYKanI6bsQ
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/safchain/ethtool v0.6.2 h1:O3ZPFAKEUEfbtE6J/feEe2Ft7dIJ2Sy8t4SdMRiIMHY=
github.com/safchain/ethtool v0.6.2/go.mod h1:VS7cn+bP3Px3rIq55xImBiZGHVLNyBh5dqG6dDQy8+I=
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1GjJohAA0p6hVEaDtHWWs=
github.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
github.com/silenceper/wechat/v2 v2.1.11 h1:KA0iuhEpwMl9L3R0Kg8KSE23CEszMbnhjBf/L2EJnSw=
github.com/silenceper/wechat/v2 v2.1.11/go.mod h1:7Iu3EhQYVtDUJAj+ZVRy8yom75ga7aDWv8RurLkVm0s=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/slack-go/slack v0.17.3 h1:zV5qO3Q+WJAQ/XwbGfNFrRMaJ5T/naqaonyPV/1TP4g=
github.com/slack-go/slack v0.17.3/go.mod h1:X+UqOufi3LYQHDnMG1vxf0J8asC6+WllXrVrhl8/Prk=
github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM=
github.com/sony/sonyflake v1.0.0/go.mod h1:Jv3cfhf/UFtolOTTRd3q4Nl6ENqM+KfyZ5PseKfZGF4=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
@@ -464,18 +716,40 @@ github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
github.com/textmagic/textmagic-rest-go-v2/v3 v3.0.43885 h1:Ve/YElmvgqg7yUQewiPfTV3PgCp1Tlu2PcPI0f8rAgg=
github.com/textmagic/textmagic-rest-go-v2/v3 v3.0.43885/go.mod h1:76AjUSm7GRxmgio8I/XbwaO5Vvw40HdSD/W3Z3M+OWo=
github.com/thedevsaddam/gojsonq/v2 v2.5.2 h1:CoMVaYyKFsVj6TjU6APqAhAvC07hTI6IQen8PHzHYY0=
github.com/thedevsaddam/gojsonq/v2 v2.5.2/go.mod h1:bv6Xa7kWy82uT0LnXPE2SzGqTj33TAEeR560MdJkiXs=
github.com/things-go/go-socks5 v0.1.0 h1:4f5dz0iMQ6cA4wseFmyLmCHmg3SWJTW92ndrKS6oERg=
github.com/things-go/go-socks5 v0.1.0/go.mod h1:Riabiyu52kLsla0YmJqunt1c1JEl6iXSr4bRd7swFEA=
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 h1:5u+EJUQiosu3JFX0XS0qTf5FznsMOzTjGqavBGuCbo0=
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2/go.mod h1:4kyMkleCiLkgY6z8gK5BkI01ChBtxR0ro3I1ZDcGM3w=
github.com/ttacon/libphonenumber v1.2.1 h1:fzOfY5zUADkCkbIafAed11gL1sW+bJ26p6zWLBMElR4=
github.com/ttacon/libphonenumber v1.2.1/go.mod h1:E0TpmdVMq5dyVlQ7oenAkhsLu86OkUl+yR4OAxyEg/M=
github.com/u-root/u-root v0.14.0 h1:Ka4T10EEML7dQ5XDvO9c3MBN8z4nuSnGjcd1jmU2ivg=
github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1WMluqE=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/utahta/go-linenotify v0.5.0 h1:E1tJaB/XhqRY/iz203FD0MaHm10DjQPOq5/Mem2A3Gs=
github.com/utahta/go-linenotify v0.5.0/go.mod h1:KsvBXil2wx+ByaCR0e+IZKTbp4pDesc7yjzRigLf6pE=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
@@ -492,18 +766,31 @@ github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zI
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
go.mau.fi/util v0.9.3 h1:aqNF8KDIN8bFpFbybSk+mEBil7IHeBwlujfyTnvP0uU=
go.mau.fi/util v0.9.3/go.mod h1:krWWfBM1jWTb5f8NCa2TLqWMQuM81X7TGQjhMjBeXmQ=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE=
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
@@ -517,11 +804,18 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A=
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM=
golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8=
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
@@ -532,57 +826,123 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.0.0-20171115151908-9dfe39835686/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20171101214715-fd80eb99c8f6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -591,7 +951,12 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -608,26 +973,50 @@ golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.257.0 h1:8Y0lzvHlZps53PEaw+G29SsQIkuKrumGWs9puiexNAA=
google.golang.org/api v0.257.0/go.mod h1:4eJrr+vbVaZSqs7vovFd1Jb/A6ml6iw2e6FBYf3GAO4=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw=
google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934=
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M=
gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
@@ -638,8 +1027,14 @@ gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlI
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
@@ -660,6 +1055,8 @@ honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0 h1:5SXjd4ET5dYijLa
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0/go.mod h1:EPDDhEZqVHhWuPI5zPAsjU0U7v9xNIWjoOVyZ5ZcniQ=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
maunium.net/go/mautrix v0.26.0 h1:valc2VmZF+oIY4bMq4Cd5H9cEKMRe8eP4FM7iiaYLxI=
maunium.net/go/mautrix v0.26.0/go.mod h1:NWMv+243NX/gDrLofJ2nNXJPrG8vzoM+WUCWph85S6Q=
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
@@ -25,6 +25,7 @@ import (
"syscall"
"github.com/bishopfox/sliver/implant/sliver/extension"
"github.com/bishopfox/sliver/implant/sliver/mount"
"github.com/bishopfox/sliver/protobuf/commonpb"
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
"google.golang.org/protobuf/proto"
@@ -56,6 +57,7 @@ var (
pb.MsgNetstatReq: netstatHandler,
pb.MsgSideloadReq: sideloadHandler,
pb.MsgMountReq: mountHandler,
pb.MsgReconfigureReq: reconfigureHandler,
pb.MsgSSHCommandReq: runSSHCommandHandler,
@@ -154,6 +156,27 @@ func listExtensionsHandler(data []byte, resp RPCResponse) {
resp(data, err)
}
func mountHandler(data []byte, resp RPCResponse) {
mountReq := &pb.MountReq{}
err := proto.Unmarshal(data, mountReq)
if err != nil {
return
}
mountData, err := mount.GetMountInformation()
mountResp := &pb.Mount{
Info: mountData,
Response: &commonpb.Response{},
}
if err != nil {
mountResp.Response.Err = err.Error()
}
data, err = proto.Marshal(mountResp)
resp(data, err)
}
func getUid(fileInfo os.FileInfo) string {
uid := int32(fileInfo.Sys().(*syscall.Stat_t).Uid)
uid_str := strconv.FormatUint(uint64(uid), 10)
@@ -36,16 +36,29 @@ import (
"google.golang.org/protobuf/proto"
)
const (
inactivityCheckInterval = 4 * time.Second
inactivityTimeout = 15 * time.Second
)
type socksTunnelPool struct {
tunnels *sync.Map // map[uint64]chan []byte
tunnels *sync.Map // map[uint64]chan []byte
lastActivity *sync.Map // map[uint64]time.Time
}
var socksTunnels = socksTunnelPool{
tunnels: &sync.Map{},
tunnels: &sync.Map{},
lastActivity: &sync.Map{},
}
var socksServer *socks5.Server
// Initialize socks server
func init() {
socksServer = socks5.NewServer()
socksTunnels.startCleanupMonitor()
}
func SocksReqHandler(envelope *sliverpb.Envelope, connection *transports.Connection) {
socksData := &sliverpb.SocksData{}
err := proto.Unmarshal(envelope.Data, socksData)
@@ -55,9 +68,26 @@ func SocksReqHandler(envelope *sliverpb.Envelope, connection *transports.Connect
// {{end}}
return
}
time.Sleep(10 * time.Millisecond) // Necessary delay
// Check early to see if this is a close request from server
if socksData.CloseConn {
if tunnel, ok := socksTunnels.tunnels.LoadAndDelete(socksData.TunnelID); ok {
if ch, ok := tunnel.(chan []byte); ok {
close(ch)
}
}
socksTunnels.lastActivity.Delete(socksData.TunnelID)
return
}
if socksData.Data == nil {
return
}
// Record activity as soon as we get data for this tunnel
socksTunnels.recordActivity(socksData.TunnelID)
// {{if .Config.Debug}}
log.Printf("[socks] User to Client to (server to implant) Data Sequence %d, Data Size %d\n", socksData.Sequence, len(socksData.Data))
// {{end}}
@@ -70,8 +100,6 @@ func SocksReqHandler(envelope *sliverpb.Envelope, connection *transports.Connect
socksServer = socks5.NewServer(
socks5.WithAuthMethods([]socks5.Authenticator{auth}),
)
} else {
socksServer = socks5.NewServer()
}
// {{if .Config.Debug}}
@@ -80,7 +108,7 @@ func SocksReqHandler(envelope *sliverpb.Envelope, connection *transports.Connect
// init tunnel
if tunnel, ok := socksTunnels.tunnels.Load(socksData.TunnelID); !ok {
tunnelChan := make(chan []byte, 10)
tunnelChan := make(chan []byte, 100) // Buffered channel for 100 messages
socksTunnels.tunnels.Store(socksData.TunnelID, tunnelChan)
tunnelChan <- socksData.Data
err := socksServer.ServeConn(&socks{stream: socksData, conn: connection})
@@ -88,9 +116,12 @@ func SocksReqHandler(envelope *sliverpb.Envelope, connection *transports.Connect
// {{if .Config.Debug}}
log.Printf("[socks] Failed to serve connection: %v", err)
// {{end}}
// Cleanup on serve failure
socksTunnels.tunnels.Delete(socksData.TunnelID)
return
}
} else {
// Will block when channel is full
tunnel.(chan []byte) <- socksData.Data
}
}
@@ -105,16 +136,22 @@ type socks struct {
}
func (s *socks) Read(b []byte) (n int, err error) {
time.Sleep(10 * time.Millisecond) // Necessary delay
channel, ok := socksTunnels.tunnels.Load(s.stream.TunnelID)
if !ok {
return 0, errors.New("[socks] invalid tunnel id")
}
socksTunnels.recordActivity(s.stream.TunnelID)
data := <-channel.(chan []byte)
return copy(b, data), nil
}
func (s *socks) Write(b []byte) (n int, err error) {
time.Sleep(10 * time.Millisecond) // Necessary delay
socksTunnels.recordActivity(s.stream.TunnelID)
data, err := proto.Marshal(&sliverpb.SocksData{
TunnelID: s.stream.TunnelID,
Data: b,
@@ -136,12 +173,15 @@ func (s *socks) Write(b []byte) (n int, err error) {
}
func (s *socks) Close() error {
time.Sleep(10 * time.Millisecond) // Necessary delay
channel, ok := socksTunnels.tunnels.LoadAndDelete(s.stream.TunnelID)
if !ok {
return errors.New("[socks] can't close unknown channel")
}
close(channel.(chan []byte))
// Signal to server that we need to close this tunnel
data, err := proto.Marshal(&sliverpb.SocksData{
TunnelID: s.stream.TunnelID,
CloseConn: true,
@@ -181,3 +221,38 @@ func (c *socks) SetReadDeadline(t time.Time) error {
func (c *socks) SetWriteDeadline(t time.Time) error {
return nil
}
func (s *socksTunnelPool) recordActivity(tunnelID uint64) {
s.lastActivity.Store(tunnelID, time.Now())
}
// Periodically check for inactive tunnels and clean up
func (s *socksTunnelPool) startCleanupMonitor() {
go func() {
ticker := time.NewTicker(inactivityCheckInterval)
defer ticker.Stop()
for range ticker.C {
s.tunnels.Range(func(key, value interface{}) bool {
tunnelID := key.(uint64)
lastActivityI, exists := s.lastActivity.Load(tunnelID)
if !exists {
// If no activity record exists, create one
s.recordActivity(tunnelID)
return true
}
lastActivity := lastActivityI.(time.Time)
if time.Since(lastActivity) > inactivityTimeout {
// Clean up the inactive tunnel
if ch, ok := value.(chan []byte); ok {
close(ch)
}
s.tunnels.Delete(tunnelID)
s.lastActivity.Delete(tunnelID)
}
return true
})
}
}()
}
+166
View File
@@ -0,0 +1,166 @@
//go:build darwin
package mount
/*
Sliver Implant Framework
Copyright (C) 2023 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"strings"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"golang.org/x/sys/unix"
)
func getString(input []byte) string {
ver := string(input)
if i := strings.Index(ver, "\x00"); i != -1 {
ver = ver[:i]
}
return ver
}
func formatMountFlags(flags uint32) string {
opts := make([]string, 0, 12)
if flags&unix.MNT_RDONLY != 0 {
opts = append(opts, "ro")
} else {
opts = append(opts, "rw")
}
if flags&unix.MNT_NOSUID != 0 {
opts = append(opts, "nosuid")
}
if flags&unix.MNT_NODEV != 0 {
opts = append(opts, "nodev")
}
if flags&unix.MNT_NOEXEC != 0 {
opts = append(opts, "noexec")
}
if flags&unix.MNT_SYNCHRONOUS != 0 {
opts = append(opts, "sync")
}
if flags&unix.MNT_ASYNC != 0 {
opts = append(opts, "async")
}
if flags&unix.MNT_NOATIME != 0 {
opts = append(opts, "noatime")
}
if flags&unix.MNT_JOURNALED != 0 {
opts = append(opts, "journaled")
}
if flags&unix.MNT_QUOTA != 0 {
opts = append(opts, "quota")
}
if flags&unix.MNT_UNION != 0 {
opts = append(opts, "union")
}
if flags&unix.MNT_AUTOMOUNTED != 0 {
opts = append(opts, "automounted")
}
if flags&unix.MNT_REMOVABLE != 0 {
opts = append(opts, "removable")
}
if flags&unix.MNT_DONTBROWSE != 0 {
opts = append(opts, "nobrowse")
}
if flags&unix.MNT_IGNORE_OWNERSHIP != 0 {
opts = append(opts, "ignore-ownership")
}
if flags&unix.MNT_LOCAL != 0 {
opts = append(opts, "local")
}
if flags&unix.MNT_ROOTFS != 0 {
opts = append(opts, "rootfs")
}
if flags&unix.MNT_SNAPSHOT != 0 {
opts = append(opts, "snapshot")
}
if flags&unix.MNT_CPROTECT != 0 {
opts = append(opts, "cprotect")
}
if flags&unix.MNT_NOUSERXATTR != 0 {
opts = append(opts, "nouserxattr")
}
if flags&unix.MNT_QUARANTINE != 0 {
opts = append(opts, "quarantine")
}
if flags&unix.MNT_UNKNOWNPERMISSIONS != 0 {
opts = append(opts, "unknown-permissions")
}
if flags&unix.MNT_DEFWRITE != 0 {
opts = append(opts, "defwrite")
}
if flags&unix.MNT_DOVOLFS != 0 {
opts = append(opts, "volfs")
}
if flags&unix.MNT_NOBLOCK != 0 {
opts = append(opts, "noblock")
}
if flags&unix.MNT_RELOAD != 0 {
opts = append(opts, "reload")
}
if flags&unix.MNT_UPDATE != 0 {
opts = append(opts, "update")
}
if flags&unix.MNT_EXPORTED != 0 {
opts = append(opts, "exported")
}
if flags&unix.MNT_MULTILABEL != 0 {
opts = append(opts, "multilabel")
}
if flags&unix.MNT_STRICTATIME != 0 {
opts = append(opts, "strictatime")
}
if flags&unix.MNT_FORCE != 0 {
opts = append(opts, "force")
}
return strings.Join(opts, ",")
}
func GetMountInformation() ([]*sliverpb.MountInfo, error) {
mountInfo := make([]*sliverpb.MountInfo, 0)
count, err := unix.Getfsstat(nil, unix.MNT_NOWAIT)
if err != nil || count == 0 {
return mountInfo, err
}
stats := make([]unix.Statfs_t, count)
count, err = unix.Getfsstat(stats, unix.MNT_NOWAIT)
if err != nil {
return mountInfo, err
}
stats = stats[:count]
for _, stat := range stats {
var mountData sliverpb.MountInfo
mountData.VolumeName = getString(stat.Mntfromname[:])
mountData.MountPoint = getString(stat.Mntonname[:])
mountData.Label = "/"
mountData.VolumeType = getString(stat.Fstypename[:])
mountData.MountOptions = formatMountFlags(stat.Flags)
mountData.TotalSpace = stat.Blocks * uint64(stat.Bsize)
mountData.FreeSpace = stat.Bavail * uint64(stat.Bsize)
mountData.UsedSpace = (stat.Blocks - stat.Bfree) * uint64(stat.Bsize)
mountInfo = append(mountInfo, &mountData)
}
return mountInfo, nil
}
+19 -1
View File
@@ -1,7 +1,25 @@
// +linux
//go:build linux
package mount
/*
Sliver Implant Framework
Copyright (C) 2023 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"bufio"
"os"
+2 -3
View File
@@ -1,5 +1,6 @@
//go:build windows
// +build windows
package mount
/*
Sliver Implant Framework
@@ -19,8 +20,6 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package mount
import (
"errors"
"fmt"
+1 -1
View File
@@ -1,4 +1,4 @@
// +darwin
//go:build darwin
package procdump
+124 -1
View File
@@ -18,6 +18,129 @@ package version
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
func GetVersion() string {
import (
"encoding/xml"
"errors"
"fmt"
//{{if .Config.Debug}}
"log"
//{{end}}
"os"
"strings"
"golang.org/x/sys/unix"
)
type plist struct {
XMLName xml.Name `xml:"plist"`
Dict dict `xml:"dict"`
}
type dict struct {
Key []string `xml:"key"`
String []string `xml:"string"`
}
func getString(input []byte) string {
ver := string(input)
if i := strings.Index(ver, "\x00"); i != -1 {
ver = ver[:i]
}
return ver
}
func readOSRelease() string {
paths := []string{
"/System/Library/CoreServices/SystemVersion.plist",
"/System/Library/CoreServices/ServerVersion.plist",
}
for _, path := range paths {
file, err := os.Open(path)
if err != nil {
continue
}
properties, err := parsePlistFile(file)
_ = file.Close()
if err != nil {
//{{if .Config.Debug}}
log.Printf("error parsing %s: %v", path, err)
//{{end}}
continue
}
if osRelease := buildOSRelease(properties); osRelease != "" {
return osRelease
}
}
return ""
}
func parsePlistFile(file *os.File) (map[string]string, error) {
var v plist
if err := xml.NewDecoder(file).Decode(&v); err != nil {
return nil, err
}
if len(v.Dict.Key) != len(v.Dict.String) {
return nil, errors.New("invalid plist content")
}
properties := make(map[string]string, len(v.Dict.Key))
for i, key := range v.Dict.Key {
properties[key] = v.Dict.String[i]
}
return properties, nil
}
func buildOSRelease(properties map[string]string) string {
productName := properties["ProductName"]
productVersion := properties["ProductVersion"]
productBuildVersion := properties["ProductBuildVersion"]
if productName == "" || productVersion == "" || productBuildVersion == "" {
return ""
}
return fmt.Sprintf("%s %s (%s)", productName, productVersion, productBuildVersion)
}
func normalizeArch(arch string) string {
arch = strings.TrimSpace(arch)
if arch == "" {
return ""
}
switch strings.ToLower(arch) {
case "amd64":
return "x86_64"
case "i386", "i486", "i586", "i686", "x86":
return "x86"
default:
return arch
}
}
func formatDetailedVersion(osRelease, kernel, arch string) string {
parts := make([]string, 0, 3)
if osRelease != "" {
parts = append(parts, osRelease)
}
if kernel != "" {
parts = append(parts, fmt.Sprintf("kernel %s", kernel))
}
if arch != "" {
parts = append(parts, arch)
}
return strings.Join(parts, " ")
}
func GetVersion() string {
osRelease := readOSRelease()
var uname unix.Utsname
if err := unix.Uname(&uname); err != nil {
//{{if .Config.Debug}}
log.Printf("error getting OS version: %v", err)
//{{end}}
return osRelease
}
if osRelease == "" {
osRelease = getString(uname.Sysname[:])
}
kernel := getString(uname.Release[:])
arch := normalizeArch(getString(uname.Machine[:]))
return formatDetailedVersion(osRelease, kernel, arch)
}
+105 -2
View File
@@ -22,7 +22,10 @@ package version
import (
"fmt"
//{{if .Config.Debug}}
"log"
//{{end}}
"os"
"strings"
"syscall"
)
@@ -39,11 +42,111 @@ func getString(input [65]int8) string {
return ver
}
func readOSRelease() string {
paths := []string{"/etc/os-release", "/usr/lib/os-release"}
for _, path := range paths {
data, err := os.ReadFile(path)
if err != nil {
continue
}
return buildOSRelease(parseOSRelease(string(data)))
}
return ""
}
func parseOSRelease(content string) map[string]string {
values := make(map[string]string)
for _, line := range strings.Split(content, "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
key, value, ok := strings.Cut(line, "=")
if !ok || key == "" {
continue
}
key = strings.TrimSpace(key)
value = unescape(unquote(strings.TrimSpace(value)))
values[key] = value
}
return values
}
func unquote(s string) string {
if len(s) < 2 {
return s
}
if (s[0] == '"' || s[0] == '\'') && s[0] == s[len(s)-1] {
return s[1 : len(s)-1]
}
return s
}
func unescape(s string) string {
return strings.NewReplacer(
`\$`, `$`,
`\"`, `"`,
`\'`, `'`,
`\\`, `\`,
"\\`", "`",
).Replace(s)
}
func buildOSRelease(values map[string]string) string {
name := values["NAME"]
version := values["VERSION"]
if version == "" {
version = values["VERSION_ID"]
}
if name != "" && version != "" {
return fmt.Sprintf("%s %s", name, version)
}
return values["PRETTY_NAME"]
}
func normalizeArch(arch string) string {
arch = strings.TrimSpace(arch)
if arch == "" {
return ""
}
switch strings.ToLower(arch) {
case "amd64":
return "x86_64"
case "i386", "i486", "i586", "i686", "x86":
return "x86"
default:
return arch
}
}
func formatDetailedVersion(osRelease, kernel, arch string) string {
parts := make([]string, 0, 3)
if osRelease != "" {
parts = append(parts, osRelease)
}
if kernel != "" {
parts = append(parts, fmt.Sprintf("kernel %s", kernel))
}
if arch != "" {
parts = append(parts, arch)
}
return strings.Join(parts, " ")
}
// GetVersion returns the os version information
func GetVersion() string {
osRelease := readOSRelease()
var uname syscall.Utsname
if err := syscall.Uname(&uname); err != nil {
log.Fatal(err)
//{{if .Config.Debug}}
log.Printf("error getting OS version: %v", err)
//{{end}}
return osRelease
}
return fmt.Sprintf("%s %s %s", getString(uname.Sysname), getString(uname.Nodename), getString(uname.Release))
if osRelease == "" {
osRelease = getString(uname.Sysname)
}
kernel := getString(uname.Release)
arch := normalizeArch(getString(uname.Machine))
return formatDetailedVersion(osRelease, kernel, arch)
}
File diff suppressed because it is too large. Load diff
+14
View File
@@ -1320,3 +1320,17 @@ message CertificateData {
message CertificateInfo {
repeated CertificateData info = 1;
}
message CertificateAuthorityData {
string CN = 1;
string CreationTime = 2;
string ValidityStart = 3;
string ValidityExpiry = 4;
string Type = 5;
string KeyAlgorithm = 6;
string ID = 7;
}
message CertificateAuthorityInfo {
repeated CertificateAuthorityData info = 1;
}
+1 -1
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.15.8
// protoc v6.33.4
// source: commonpb/common.proto
package commonpb
+1 -1
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.15.8
// protoc v6.33.4
// source: dnspb/dns.proto
package dnspb
File diff suppressed because it is too large. Load diff
+2
View File
@@ -108,6 +108,8 @@ service SliverRPC {
// *** Certificates ***
rpc GetCertificateInfo(clientpb.CertificatesReq) returns (clientpb.CertificateInfo);
rpc GetCertificateAuthorityInfo(commonpb.Empty)
returns (clientpb.CertificateAuthorityInfo);
// *** Crackstation ***
rpc CrackstationRegister(clientpb.Crackstation)
+36
View File
@@ -96,6 +96,7 @@ type SliverRPCClient interface {
Builders(ctx context.Context, in *commonpb.Empty, opts ...grpc.CallOption) (*clientpb.Builders, error)
// *** Certificates ***
GetCertificateInfo(ctx context.Context, in *clientpb.CertificatesReq, opts ...grpc.CallOption) (*clientpb.CertificateInfo, error)
GetCertificateAuthorityInfo(ctx context.Context, in *commonpb.Empty, opts ...grpc.CallOption) (*clientpb.CertificateAuthorityInfo, error)
// *** Crackstation ***
CrackstationRegister(ctx context.Context, in *clientpb.Crackstation, opts ...grpc.CallOption) (SliverRPC_CrackstationRegisterClient, error)
CrackstationTrigger(ctx context.Context, in *clientpb.Event, opts ...grpc.CallOption) (*commonpb.Empty, error)
@@ -812,6 +813,15 @@ func (c *sliverRPCClient) GetCertificateInfo(ctx context.Context, in *clientpb.C
return out, nil
}
func (c *sliverRPCClient) GetCertificateAuthorityInfo(ctx context.Context, in *commonpb.Empty, opts ...grpc.CallOption) (*clientpb.CertificateAuthorityInfo, error) {
out := new(clientpb.CertificateAuthorityInfo)
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/GetCertificateAuthorityInfo", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *sliverRPCClient) CrackstationRegister(ctx context.Context, in *clientpb.Crackstation, opts ...grpc.CallOption) (SliverRPC_CrackstationRegisterClient, error) {
stream, err := c.cc.NewStream(ctx, &SliverRPC_ServiceDesc.Streams[2], "/rpcpb.SliverRPC/CrackstationRegister", opts...)
if err != nil {
@@ -2088,6 +2098,7 @@ type SliverRPCServer interface {
Builders(context.Context, *commonpb.Empty) (*clientpb.Builders, error)
// *** Certificates ***
GetCertificateInfo(context.Context, *clientpb.CertificatesReq) (*clientpb.CertificateInfo, error)
GetCertificateAuthorityInfo(context.Context, *commonpb.Empty) (*clientpb.CertificateAuthorityInfo, error)
// *** Crackstation ***
CrackstationRegister(*clientpb.Crackstation, SliverRPC_CrackstationRegisterServer) error
CrackstationTrigger(context.Context, *clientpb.Event) (*commonpb.Empty, error)
@@ -2405,6 +2416,9 @@ func (UnimplementedSliverRPCServer) Builders(context.Context, *commonpb.Empty) (
func (UnimplementedSliverRPCServer) GetCertificateInfo(context.Context, *clientpb.CertificatesReq) (*clientpb.CertificateInfo, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetCertificateInfo not implemented")
}
func (UnimplementedSliverRPCServer) GetCertificateAuthorityInfo(context.Context, *commonpb.Empty) (*clientpb.CertificateAuthorityInfo, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetCertificateAuthorityInfo not implemented")
}
func (UnimplementedSliverRPCServer) CrackstationRegister(*clientpb.Crackstation, SliverRPC_CrackstationRegisterServer) error {
return status.Errorf(codes.Unimplemented, "method CrackstationRegister not implemented")
}
@@ -3842,6 +3856,24 @@ func _SliverRPC_GetCertificateInfo_Handler(srv interface{}, ctx context.Context,
return interceptor(ctx, in, info, handler)
}
func _SliverRPC_GetCertificateAuthorityInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(commonpb.Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SliverRPCServer).GetCertificateAuthorityInfo(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/rpcpb.SliverRPC/GetCertificateAuthorityInfo",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SliverRPCServer).GetCertificateAuthorityInfo(ctx, req.(*commonpb.Empty))
}
return interceptor(ctx, in, info, handler)
}
func _SliverRPC_CrackstationRegister_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(clientpb.Crackstation)
if err := stream.RecvMsg(m); err != nil {
@@ -6309,6 +6341,10 @@ var SliverRPC_ServiceDesc = grpc.ServiceDesc{
MethodName: "GetCertificateInfo",
Handler: _SliverRPC_GetCertificateInfo_Handler,
},
{
MethodName: "GetCertificateAuthorityInfo",
Handler: _SliverRPC_GetCertificateAuthorityInfo_Handler,
},
{
MethodName: "CrackstationTrigger",
Handler: _SliverRPC_CrackstationTrigger_Handler,
+1 -1
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.15.8
// protoc v6.33.4
// source: sliverpb/sliver.proto
package sliverpb
+7 -1
View File
@@ -22,6 +22,7 @@ import (
"os"
"path/filepath"
"github.com/bishopfox/sliver/server/assets"
"github.com/bishopfox/sliver/server/log"
"golang.org/x/crypto/acme/autocert"
)
@@ -37,7 +38,12 @@ var (
// GetACMEDir - Dir to store ACME certs
func GetACMEDir() string {
acmePath := filepath.Join(getCertDir(), ACMEDirName)
certDir := getCertDir()
baseDir := certDir
if fi, err := os.Stat(certDir); err != nil || !fi.IsDir() {
baseDir = assets.GetRootAppDir()
}
acmePath := filepath.Join(baseDir, ACMEDirName)
if _, err := os.Stat(acmePath); os.IsNotExist(err) {
acmeLog.Infof("[mkdir] %s", acmePath)
os.MkdirAll(acmePath, 0700)
+134 -40
View File
@@ -22,19 +22,23 @@ import (
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"os"
"path/filepath"
"github.com/bishopfox/sliver/server/assets"
"github.com/bishopfox/sliver/server/db"
"github.com/bishopfox/sliver/server/db/models"
)
// -----------------------
// CERTIFICATE AUTHORITY
// -----------------------
// SetupCAs - Creates directories for certs
// SetupCAs - Ensure certificate authorities exist in storage
func SetupCAs() {
importCertificateAuthoritiesFromDisk()
GenerateCertificateAuthority(MtlsImplantCA, "")
GenerateCertificateAuthority(MtlsServerCA, "")
GenerateCertificateAuthority(OperatorCA, "operators")
@@ -43,24 +47,17 @@ func SetupCAs() {
func getCertDir() string {
rootDir := assets.GetRootAppDir()
certDir := filepath.Join(rootDir, "certs")
if _, err := os.Stat(certDir); os.IsNotExist(err) {
err := os.MkdirAll(certDir, 0700)
if err != nil {
certsLog.Fatalf("Failed to create cert dir %s", err)
}
}
return certDir
return filepath.Join(rootDir, "certs")
}
// GenerateCertificateAuthority - Creates a new CA cert for a given type
func GenerateCertificateAuthority(caType string, commonName string) (*x509.Certificate, *ecdsa.PrivateKey) {
storageDir := getCertDir()
certFilePath := filepath.Join(storageDir, fmt.Sprintf("%s-ca-cert.pem", caType))
if _, err := os.Stat(certFilePath); os.IsNotExist(err) {
certsLog.Infof("Generating certificate authority for '%s'", caType)
cert, key := GenerateECCCertificate(caType, commonName, true, false, false)
SaveCertificateAuthority(caType, cert, key)
if !certificateAuthorityExists(caType) {
if !importCertificateAuthorityFromDisk(caType) {
certsLog.Infof("Generating certificate authority for '%s'", caType)
cert, key := GenerateECCCertificate(caType, commonName, true, false, false)
SaveCertificateAuthority(caType, cert, key)
}
}
cert, key, err := GetCertificateAuthority(caType)
if err != nil {
@@ -104,45 +101,142 @@ func GetCertificateAuthority(caType string) (*x509.Certificate, *ecdsa.PrivateKe
// GetCertificateAuthorityPEM - Get PEM encoded CA cert/key
func GetCertificateAuthorityPEM(caType string) ([]byte, []byte, error) {
caType = filepath.Base(caType)
caCertPath := filepath.Join(getCertDir(), fmt.Sprintf("%s-ca-cert.pem", caType))
caKeyPath := filepath.Join(getCertDir(), fmt.Sprintf("%s-ca-key.pem", caType))
caModel := &models.CertificateAuthority{}
dbSession := db.Session()
result := dbSession.Where(&models.CertificateAuthority{CAType: caType}).First(caModel)
if result.Error == nil {
return []byte(caModel.CertificatePEM), []byte(caModel.PrivateKeyPEM), nil
}
if !errors.Is(result.Error, db.ErrRecordNotFound) {
certsLog.Error(result.Error)
return nil, nil, result.Error
}
certPEM, keyPEM, err := loadCertificateAuthorityPEMFromDisk(caType)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
certsLog.Error(err)
}
return nil, nil, err
}
SaveCertificateAuthority(caType, certPEM, keyPEM)
return certPEM, keyPEM, nil
}
// SaveCertificateAuthority - Save the certificate and the key to the database
// doesn't return an error because errors are fatal. If we can't generate CAs,
// then we can't secure communication and we should die a horrible death.
func SaveCertificateAuthority(caType string, cert []byte, key []byte) {
caType = filepath.Base(caType)
commonName := commonNameFromCertPEM(cert)
dbSession := db.Session()
caModel := &models.CertificateAuthority{}
result := dbSession.Where(&models.CertificateAuthority{CAType: caType}).First(caModel)
if errors.Is(result.Error, db.ErrRecordNotFound) {
caModel = &models.CertificateAuthority{
CommonName: commonName,
CAType: caType,
CertificatePEM: string(cert),
PrivateKeyPEM: string(key),
}
if err := dbSession.Create(caModel).Error; err != nil {
certsLog.Fatalf("Failed to save certificate authority: %s", err)
}
return
}
if result.Error != nil {
certsLog.Fatalf("Failed to save certificate authority: %s", result.Error)
}
caModel.CommonName = commonName
caModel.CertificatePEM = string(cert)
caModel.PrivateKeyPEM = string(key)
if err := dbSession.Save(caModel).Error; err != nil {
certsLog.Fatalf("Failed to save certificate authority: %s", err)
}
}
func certificateAuthorityExists(caType string) bool {
caType = filepath.Base(caType)
dbSession := db.Session()
result := dbSession.Select("id").Where(&models.CertificateAuthority{CAType: caType}).First(&models.CertificateAuthority{})
if result.Error == nil {
return true
}
if !errors.Is(result.Error, db.ErrRecordNotFound) {
certsLog.Error(result.Error)
return true
}
return false
}
func importCertificateAuthoritiesFromDisk() {
certDir := getCertDir()
fi, err := os.Stat(certDir)
if err != nil || !fi.IsDir() {
return
}
importCertificateAuthorityFromDisk(MtlsImplantCA)
importCertificateAuthorityFromDisk(MtlsServerCA)
importCertificateAuthorityFromDisk(OperatorCA)
importCertificateAuthorityFromDisk(HTTPSCA)
}
func importCertificateAuthorityFromDisk(caType string) bool {
if certificateAuthorityExists(caType) {
return false
}
certPEM, keyPEM, err := loadCertificateAuthorityPEMFromDisk(caType)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
certsLog.Error(err)
}
return false
}
certsLog.Infof("Importing certificate authority for '%s' from disk", caType)
SaveCertificateAuthority(caType, certPEM, keyPEM)
return true
}
func loadCertificateAuthorityPEMFromDisk(caType string) ([]byte, []byte, error) {
caType = filepath.Base(caType)
certDir := getCertDir()
fi, err := os.Stat(certDir)
if err != nil || !fi.IsDir() {
return nil, nil, os.ErrNotExist
}
caCertPath := filepath.Join(certDir, fmt.Sprintf("%s-ca-cert.pem", caType))
caKeyPath := filepath.Join(certDir, fmt.Sprintf("%s-ca-key.pem", caType))
certPEM, err := os.ReadFile(caCertPath)
if err != nil {
certsLog.Error(err)
if os.IsNotExist(err) {
return nil, nil, os.ErrNotExist
}
return nil, nil, err
}
keyPEM, err := os.ReadFile(caKeyPath)
if err != nil {
certsLog.Error(err)
if os.IsNotExist(err) {
return nil, nil, os.ErrNotExist
}
return nil, nil, err
}
return certPEM, keyPEM, nil
}
// SaveCertificateAuthority - Save the certificate and the key to the filesystem
// doesn't return an error because errors are fatal. If we can't generate CAs,
// then we can't secure communication and we should die a horrible death.
func SaveCertificateAuthority(caType string, cert []byte, key []byte) {
storageDir := getCertDir()
if _, err := os.Stat(storageDir); os.IsNotExist(err) {
os.MkdirAll(storageDir, 0700)
func commonNameFromCertPEM(cert []byte) string {
block, _ := pem.Decode(cert)
if block == nil {
return ""
}
// CAs get written to the filesystem since we control the names and makes them
// easier to move around/backup
certFilePath := filepath.Join(storageDir, fmt.Sprintf("%s-ca-cert.pem", caType))
keyFilePath := filepath.Join(storageDir, fmt.Sprintf("%s-ca-key.pem", caType))
err := os.WriteFile(certFilePath, cert, 0600)
parsed, err := x509.ParseCertificate(block.Bytes)
if err != nil {
certsLog.Fatalf("Failed write certificate data to: %s", certFilePath)
}
err = os.WriteFile(keyFilePath, key, 0600)
if err != nil {
certsLog.Fatalf("Failed write certificate data to: %s", keyFilePath)
return ""
}
return parsed.Subject.CommonName
}
+8
View File
@@ -92,9 +92,17 @@ var cmdImportCA = &cobra.Command{
fmt.Printf("Failed to parse file %s", err)
os.Exit(1)
}
if strings.TrimSpace(importCA.Certificate) == "" || strings.TrimSpace(importCA.PrivateKey) == "" {
fmt.Printf("CA file missing certificate or private key data\n")
os.Exit(1)
}
cert := []byte(importCA.Certificate)
key := []byte(importCA.PrivateKey)
certs.SaveCertificateAuthority(ca, cert, key)
if _, _, err := certs.GetCertificateAuthorityPEM(ca); err != nil {
fmt.Printf("Failed to verify imported CA %s\n", err)
os.Exit(1)
}
},
}
+1
View File
@@ -136,6 +136,7 @@ var rootCmd = &cobra.Command{
cryptography.AgeServerKeyPair()
cryptography.MinisignServerPrivateKey()
c2.SetupDefaultC2Profiles()
_, _ = configs.LoadCrackConfig()
serverConfig := configs.GetServerConfig()
listenerJobs, err := db.ListenerJobs()
+2
View File
@@ -11,6 +11,7 @@ import (
"github.com/bishopfox/sliver/server/assets"
"github.com/bishopfox/sliver/server/c2"
"github.com/bishopfox/sliver/server/certs"
"github.com/bishopfox/sliver/server/configs"
"github.com/bishopfox/sliver/server/console"
"github.com/bishopfox/sliver/server/cryptography"
"github.com/bishopfox/sliver/server/daemon"
@@ -63,6 +64,7 @@ var daemonCmd = &cobra.Command{
certs.SetupWGKeys()
cryptography.AgeServerKeyPair()
cryptography.MinisignServerPrivateKey()
_, _ = configs.LoadCrackConfig()
listenerJobs, err := db.ListenerJobs()
if err != nil {
+83 -25
View File
@@ -26,17 +26,19 @@ import (
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/server/assets"
"github.com/bishopfox/sliver/server/log"
"gopkg.in/yaml.v3"
)
const (
crackConfigFileName = "crack.json"
crackConfigFileName = "crack.yaml"
crackLegacyConfigFileName = "crack.json"
)
var (
crackConfigLog = log.NamedLogger("config", "crack")
)
// getCrackConfigPath - File path to config.json
// getCrackConfigPath - File path to config.yaml
func getCrackConfigPath() string {
appDir := assets.GetRootAppDir()
confPath := filepath.Join(appDir, "configs", crackConfigFileName)
@@ -44,6 +46,41 @@ func getCrackConfigPath() string {
return confPath
}
func getCrackLegacyConfigPath() string {
appDir := assets.GetRootAppDir()
return filepath.Join(appDir, "configs", crackLegacyConfigFileName)
}
type crackConfigYAML struct {
AutoFire bool `yaml:"AutoFire"`
MaxFileSize int64 `yaml:"MaxFileSize"`
ChunkSize int64 `yaml:"ChunkSize"`
MaxDiskUsage int64 `yaml:"MaxDiskUsage"`
}
func crackConfigFromYAML(data []byte) (*clientpb.CrackConfig, error) {
var raw crackConfigYAML
if err := yaml.Unmarshal(data, &raw); err != nil {
return nil, err
}
return &clientpb.CrackConfig{
AutoFire: raw.AutoFire,
MaxFileSize: raw.MaxFileSize,
ChunkSize: raw.ChunkSize,
MaxDiskUsage: raw.MaxDiskUsage,
}, nil
}
func crackConfigToYAML(config *clientpb.CrackConfig) ([]byte, error) {
raw := crackConfigYAML{
AutoFire: config.AutoFire,
MaxFileSize: config.MaxFileSize,
ChunkSize: config.ChunkSize,
MaxDiskUsage: config.MaxDiskUsage,
}
return yaml.Marshal(raw)
}
// Save - Save config file to disk
func SaveCrackConfig(config *clientpb.CrackConfig) error {
configPath := getCrackConfigPath()
@@ -55,7 +92,7 @@ func SaveCrackConfig(config *clientpb.CrackConfig) error {
return err
}
}
data, err := json.MarshalIndent(config, "", " ")
data, err := crackConfigToYAML(config)
if err != nil {
return err
}
@@ -70,6 +107,7 @@ func SaveCrackConfig(config *clientpb.CrackConfig) error {
// LoadCrackConfig - Get config value
func LoadCrackConfig() (*clientpb.CrackConfig, error) {
configPath := getCrackConfigPath()
legacyPath := getCrackLegacyConfigPath()
const defaultMaxFileSize = 10 * 1024 * 1024 * 1024 // 10GB - max size of any one file
const defaultMaxDiskUsage = 5 * defaultMaxFileSize // 50GB - total disk usage for all files
const maxChunkSize = 1024 * 1024 * 1024 // 1GB - max size of any one chunk
@@ -80,15 +118,28 @@ func LoadCrackConfig() (*clientpb.CrackConfig, error) {
ChunkSize: defaultChunkSize,
MaxDiskUsage: defaultMaxDiskUsage,
}
migratedLegacy := false
if _, err := os.Stat(configPath); !os.IsNotExist(err) {
data, err := os.ReadFile(configPath)
if err != nil {
crackConfigLog.Errorf("Failed to read crack config file %s", err)
return config, err
}
err = json.Unmarshal(data, config)
parsed, err := crackConfigFromYAML(data)
if err != nil {
crackConfigLog.Errorf("Failed to parse crack config file %s", err)
return config, err
}
config = parsed
} else if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
data, err := os.ReadFile(legacyPath)
if err != nil {
crackConfigLog.Errorf("Failed to read legacy crack config file %s", err)
return config, err
}
err = json.Unmarshal(data, config)
if err != nil {
crackConfigLog.Errorf("Failed to parse legacy crack config file %s", err)
return &clientpb.CrackConfig{
AutoFire: true,
MaxFileSize: defaultMaxFileSize,
@@ -96,33 +147,40 @@ func LoadCrackConfig() (*clientpb.CrackConfig, error) {
MaxDiskUsage: defaultMaxDiskUsage,
}, err
}
if config.ChunkSize < 1024 {
crackConfigLog.Warnf("Invalid chunk size %d, using default %d", config.ChunkSize, defaultChunkSize)
config.ChunkSize = defaultChunkSize
}
if config.MaxFileSize < 1024 {
crackConfigLog.Warnf("Invalid max file size %d, using default %d", config.MaxFileSize, defaultMaxFileSize)
config.MaxFileSize = defaultMaxFileSize
}
if maxChunkSize < config.ChunkSize {
crackConfigLog.Warnf("Chunk size is too large, setting to %d", maxChunkSize)
config.ChunkSize = maxChunkSize
}
if config.MaxFileSize < config.ChunkSize {
crackConfigLog.Warnf("Chunk size is larger than max file size, setting to %d", config.MaxFileSize)
config.ChunkSize = config.MaxFileSize
}
if config.MaxDiskUsage < config.MaxFileSize {
crackConfigLog.Warnf("Max disk usage is smaller than max file size, setting to %d", config.MaxFileSize)
config.MaxDiskUsage = config.MaxFileSize
}
return config, nil
migratedLegacy = true
crackConfigLog.Infof("Migrating legacy config %s to %s", legacyPath, configPath)
} else {
crackConfigLog.Debugf("Crack config file does not exist, using defaults")
}
if config.ChunkSize < 1024 {
crackConfigLog.Warnf("Invalid chunk size %d, using default %d", config.ChunkSize, defaultChunkSize)
config.ChunkSize = defaultChunkSize
}
if config.MaxFileSize < 1024 {
crackConfigLog.Warnf("Invalid max file size %d, using default %d", config.MaxFileSize, defaultMaxFileSize)
config.MaxFileSize = defaultMaxFileSize
}
if maxChunkSize < config.ChunkSize {
crackConfigLog.Warnf("Chunk size is too large, setting to %d", maxChunkSize)
config.ChunkSize = maxChunkSize
}
if config.MaxFileSize < config.ChunkSize {
crackConfigLog.Warnf("Chunk size is larger than max file size, setting to %d", config.MaxFileSize)
config.ChunkSize = config.MaxFileSize
}
if config.MaxDiskUsage < config.MaxFileSize {
crackConfigLog.Warnf("Max disk usage is smaller than max file size, setting to %d", config.MaxFileSize)
config.MaxDiskUsage = config.MaxFileSize
}
err := SaveCrackConfig(config) // This updates the config with any missing fields
if err != nil {
crackConfigLog.Errorf("Failed to save default config %s", err)
return config, err
}
if migratedLegacy {
if err := renameLegacyConfig(legacyPath); err != nil {
crackConfigLog.Errorf("Failed to rename legacy config %s", err)
}
}
return config, nil
}
+101
View File
@@ -0,0 +1,101 @@
package configs
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/bishopfox/sliver/protobuf/clientpb"
)
func TestCrackConfigParsesYAML(t *testing.T) {
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
configPath := getCrackConfigPath()
if err := os.MkdirAll(filepath.Dir(configPath), 0700); err != nil {
t.Fatalf("failed to create config dir: %v", err)
}
data := []byte(`AutoFire: false
MaxFileSize: 2048
ChunkSize: 1024
MaxDiskUsage: 4096
`)
if err := os.WriteFile(configPath, data, 0600); err != nil {
t.Fatalf("failed to write yaml config: %v", err)
}
config, err := LoadCrackConfig()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if config.AutoFire {
t.Fatalf("expected AutoFire false")
}
if config.MaxFileSize != 2048 {
t.Fatalf("expected MaxFileSize %d, got %d", 2048, config.MaxFileSize)
}
if config.ChunkSize != 1024 {
t.Fatalf("expected ChunkSize %d, got %d", 1024, config.ChunkSize)
}
if config.MaxDiskUsage != 4096 {
t.Fatalf("expected MaxDiskUsage %d, got %d", 4096, config.MaxDiskUsage)
}
}
func TestCrackConfigWritesDefault(t *testing.T) {
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
config, err := LoadCrackConfig()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if config == nil {
t.Fatalf("expected default crack config")
}
if _, err := os.Stat(getCrackConfigPath()); err != nil {
t.Fatalf("expected default config file to exist: %v", err)
}
}
func TestCrackConfigMigratesLegacyJSON(t *testing.T) {
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
configDir := filepath.Dir(getCrackConfigPath())
if err := os.MkdirAll(configDir, 0700); err != nil {
t.Fatalf("failed to create config dir: %v", err)
}
legacyPath := filepath.Join(configDir, crackLegacyConfigFileName)
legacy := &clientpb.CrackConfig{
AutoFire: false,
MaxFileSize: 4096,
ChunkSize: 2048,
MaxDiskUsage: 8192,
}
data, err := json.MarshalIndent(legacy, "", " ")
if err != nil {
t.Fatalf("failed to marshal legacy config: %v", err)
}
if err := os.WriteFile(legacyPath, data, 0600); err != nil {
t.Fatalf("failed to write legacy config: %v", err)
}
config, err := LoadCrackConfig()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if config.MaxFileSize != 4096 {
t.Fatalf("expected MaxFileSize %d, got %d", 4096, config.MaxFileSize)
}
if _, err := os.Stat(getCrackConfigPath()); err != nil {
t.Fatalf("expected migrated yaml config file to exist: %v", err)
}
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
t.Fatalf("expected legacy config to be renamed: %v", err)
}
if _, err := os.Stat(legacyBackupPath(legacyPath)); err != nil {
t.Fatalf("expected legacy backup file to exist: %v", err)
}
}
+43 -15
View File
@@ -30,6 +30,7 @@ import (
"github.com/bishopfox/sliver/server/assets"
"github.com/bishopfox/sliver/server/log"
"gopkg.in/yaml.v3"
)
const (
@@ -40,7 +41,8 @@ const (
// MySQL - MySQL protocol
MySQL = "mysql"
databaseConfigFileName = "database.json"
databaseConfigFileName = "database.yaml"
databaseLegacyConfigFileName = "database.json"
)
var (
@@ -57,7 +59,7 @@ var (
}
)
// GetDatabaseConfigPath - File path to config.json
// GetDatabaseConfigPath - File path to config.yaml
func GetDatabaseConfigPath() string {
appDir := assets.GetRootAppDir()
databaseConfigPath := filepath.Join(appDir, "configs", databaseConfigFileName)
@@ -65,22 +67,27 @@ func GetDatabaseConfigPath() string {
return databaseConfigPath
}
func getDatabaseLegacyConfigPath() string {
appDir := assets.GetRootAppDir()
return filepath.Join(appDir, "configs", databaseLegacyConfigFileName)
}
// DatabaseConfig - Server config
type DatabaseConfig struct {
Dialect string `json:"dialect"`
Database string `json:"database"`
Username string `json:"username"`
Password string `json:"password"`
Host string `json:"host"`
Port uint16 `json:"port"`
Dialect string `json:"dialect" yaml:"dialect"`
Database string `json:"database" yaml:"database"`
Username string `json:"username" yaml:"username"`
Password string `json:"password" yaml:"password"`
Host string `json:"host" yaml:"host"`
Port uint16 `json:"port" yaml:"port"`
Params map[string]string `json:"params"`
Pragmas map[string]string `json:"pragmas"`
Params map[string]string `json:"params" yaml:"params"`
Pragmas map[string]string `json:"pragmas" yaml:"pragmas"`
MaxIdleConns int `json:"max_idle_conns"`
MaxOpenConns int `json:"max_open_conns"`
MaxIdleConns int `json:"max_idle_conns" yaml:"max_idle_conns"`
MaxOpenConns int `json:"max_open_conns" yaml:"max_open_conns"`
LogLevel string `json:"log_level"`
LogLevel string `json:"log_level" yaml:"log_level"`
}
// DSN - Get the db connections string
@@ -170,7 +177,7 @@ func (c *DatabaseConfig) Save() error {
return err
}
}
data, err := json.MarshalIndent(c, "", " ")
data, err := yaml.Marshal(c)
if err != nil {
return err
}
@@ -185,18 +192,33 @@ func (c *DatabaseConfig) Save() error {
// GetDatabaseConfig - Get config value
func GetDatabaseConfig() *DatabaseConfig {
configPath := GetDatabaseConfigPath()
legacyPath := getDatabaseLegacyConfigPath()
config := getDefaultDatabaseConfig()
migratedLegacy := false
if _, err := os.Stat(configPath); !os.IsNotExist(err) {
data, err := os.ReadFile(configPath)
if err != nil {
databaseConfigLog.Errorf("Failed to read config file %s", err)
return config
}
err = json.Unmarshal(data, config)
err = yaml.Unmarshal(data, config)
if err != nil {
databaseConfigLog.Errorf("Failed to parse config file %s", err)
return config
}
} else if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
data, err := os.ReadFile(legacyPath)
if err != nil {
databaseConfigLog.Errorf("Failed to read legacy config file %s", err)
return config
}
err = json.Unmarshal(data, config)
if err != nil {
databaseConfigLog.Errorf("Failed to parse legacy config file %s", err)
return config
}
migratedLegacy = true
databaseConfigLog.Infof("Migrating legacy config %s to %s", legacyPath, configPath)
} else {
databaseConfigLog.Warnf("Config file does not exist, using defaults")
}
@@ -213,6 +235,12 @@ func GetDatabaseConfig() *DatabaseConfig {
err := config.Save() // This updates the config with any missing fields
if err != nil {
databaseConfigLog.Errorf("Failed to save default config %s", err)
return config
}
if migratedLegacy {
if err := renameLegacyConfig(legacyPath); err != nil {
databaseConfigLog.Errorf("Failed to rename legacy config %s", err)
}
}
return config
}
+129
View File
@@ -0,0 +1,129 @@
package configs
import (
"encoding/json"
"os"
"path/filepath"
"testing"
)
func TestDatabaseConfigParsesYAML(t *testing.T) {
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
configPath := GetDatabaseConfigPath()
if err := os.MkdirAll(filepath.Dir(configPath), 0700); err != nil {
t.Fatalf("failed to create config dir: %v", err)
}
data := []byte(`dialect: mysql
database: "sliver"
username: "tester"
password: "secret"
host: "127.0.0.1"
port: 3306
params:
charset: "utf8mb4"
pragmas: {}
max_idle_conns: 7
max_open_conns: 15
log_level: "info"
`)
if err := os.WriteFile(configPath, data, 0600); err != nil {
t.Fatalf("failed to write yaml config: %v", err)
}
config := GetDatabaseConfig()
if config.Dialect != MySQL {
t.Fatalf("expected dialect %q, got %q", MySQL, config.Dialect)
}
if config.Database != "sliver" {
t.Fatalf("expected database %q, got %q", "sliver", config.Database)
}
if config.Username != "tester" {
t.Fatalf("expected username %q, got %q", "tester", config.Username)
}
if config.Password != "secret" {
t.Fatalf("expected password %q, got %q", "secret", config.Password)
}
if config.Host != "127.0.0.1" {
t.Fatalf("expected host %q, got %q", "127.0.0.1", config.Host)
}
if config.Port != 3306 {
t.Fatalf("expected port %d, got %d", 3306, config.Port)
}
if config.Params["charset"] != "utf8mb4" {
t.Fatalf("expected charset param %q, got %q", "utf8mb4", config.Params["charset"])
}
if config.MaxIdleConns != 7 {
t.Fatalf("expected max_idle_conns %d, got %d", 7, config.MaxIdleConns)
}
if config.MaxOpenConns != 15 {
t.Fatalf("expected max_open_conns %d, got %d", 15, config.MaxOpenConns)
}
if config.LogLevel != "info" {
t.Fatalf("expected log_level %q, got %q", "info", config.LogLevel)
}
}
func TestDatabaseConfigWritesDefault(t *testing.T) {
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
config := GetDatabaseConfig()
if config.Dialect != Sqlite {
t.Fatalf("expected default dialect %q, got %q", Sqlite, config.Dialect)
}
if _, err := os.Stat(GetDatabaseConfigPath()); err != nil {
t.Fatalf("expected default config file to exist: %v", err)
}
}
func TestDatabaseConfigMigratesLegacyJSON(t *testing.T) {
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
configDir := filepath.Dir(GetDatabaseConfigPath())
if err := os.MkdirAll(configDir, 0700); err != nil {
t.Fatalf("failed to create config dir: %v", err)
}
legacyPath := filepath.Join(configDir, databaseLegacyConfigFileName)
legacy := &DatabaseConfig{
Dialect: Postgres,
Database: "sliver",
Username: "legacy",
Password: "pass",
Host: "db.internal",
Port: 5432,
Params: map[string]string{"sslmode": "disable"},
Pragmas: map[string]string{},
MaxIdleConns: 4,
MaxOpenConns: 9,
LogLevel: "debug",
}
data, err := json.MarshalIndent(legacy, "", " ")
if err != nil {
t.Fatalf("failed to marshal legacy config: %v", err)
}
if err := os.WriteFile(legacyPath, data, 0600); err != nil {
t.Fatalf("failed to write legacy config: %v", err)
}
config := GetDatabaseConfig()
if config.Dialect != Postgres {
t.Fatalf("expected dialect %q, got %q", Postgres, config.Dialect)
}
if config.Database != "sliver" {
t.Fatalf("expected database %q, got %q", "sliver", config.Database)
}
if config.Params["sslmode"] != "disable" {
t.Fatalf("expected sslmode param %q, got %q", "disable", config.Params["sslmode"])
}
if _, err := os.Stat(GetDatabaseConfigPath()); err != nil {
t.Fatalf("expected migrated yaml config file to exist: %v", err)
}
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
t.Fatalf("expected legacy config to be renamed: %v", err)
}
if _, err := os.Stat(legacyBackupPath(legacyPath)); err != nil {
t.Fatalf("expected legacy backup file to exist: %v", err)
}
}
+27
View File
@@ -0,0 +1,27 @@
package configs
import (
"os"
"path/filepath"
"strings"
)
func legacyBackupPath(legacyPath string) string {
dir := filepath.Dir(legacyPath)
base := filepath.Base(legacyPath)
name := strings.TrimSuffix(base, filepath.Ext(base))
return filepath.Join(dir, "."+name+".json-old")
}
func renameLegacyConfig(legacyPath string) error {
if _, err := os.Stat(legacyPath); os.IsNotExist(err) {
return nil
}
backupPath := legacyBackupPath(legacyPath)
if _, err := os.Stat(backupPath); err == nil {
if err := os.Remove(backupPath); err != nil {
return err
}
}
return os.Rename(legacyPath, backupPath)
}
+398
View File
@@ -0,0 +1,398 @@
package configs
// NotificationsConfig - Notification configuration for the server.
type NotificationsConfig struct {
Enabled bool `json:"enabled" yaml:"enabled"`
Events []string `json:"events,omitempty" yaml:"events,omitempty"`
Templates map[string]*NotificationTemplateConfig `json:"templates,omitempty" yaml:"templates,omitempty"`
Services *NotificationsServicesConfig `json:"services,omitempty" yaml:"services,omitempty"`
}
// NotificationServiceConfig - Shared settings for notification services.
type NotificationServiceConfig struct {
Enabled bool `json:"enabled" yaml:"enabled"`
Events []string `json:"events,omitempty" yaml:"events,omitempty"`
}
// NotificationTemplateConfig - Per-event template configuration.
type NotificationTemplateConfig struct {
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Template string `json:"template" yaml:"template"`
}
// NotificationsServicesConfig - Available notification services.
type NotificationsServicesConfig struct {
AmazonSES *AmazonSESConfig `json:"amazon_ses,omitempty" yaml:"amazon_ses,omitempty"`
AmazonSNS *AmazonSNSConfig `json:"amazon_sns,omitempty" yaml:"amazon_sns,omitempty"`
Bark *BarkConfig `json:"bark,omitempty" yaml:"bark,omitempty"`
DingDing *DingDingConfig `json:"dingding,omitempty" yaml:"dingding,omitempty"`
Discord *DiscordConfig `json:"discord,omitempty" yaml:"discord,omitempty"`
FCM *FCMConfig `json:"fcm,omitempty" yaml:"fcm,omitempty"`
GoogleChat *GoogleChatConfig `json:"google_chat,omitempty" yaml:"google_chat,omitempty"`
HTTP *HTTPConfig `json:"http,omitempty" yaml:"http,omitempty"`
Lark *LarkConfig `json:"lark,omitempty" yaml:"lark,omitempty"`
Line *LineConfig `json:"line,omitempty" yaml:"line,omitempty"`
LineNotify *LineNotifyConfig `json:"line_notify,omitempty" yaml:"line_notify,omitempty"`
Mail *MailConfig `json:"mail,omitempty" yaml:"mail,omitempty"`
Mailgun *MailgunConfig `json:"mailgun,omitempty" yaml:"mailgun,omitempty"`
Matrix *MatrixConfig `json:"matrix,omitempty" yaml:"matrix,omitempty"`
Mattermost *MattermostConfig `json:"mattermost,omitempty" yaml:"mattermost,omitempty"`
MSTeams *MSTeamsConfig `json:"msteams,omitempty" yaml:"msteams,omitempty"`
PagerDuty *PagerDutyConfig `json:"pagerduty,omitempty" yaml:"pagerduty,omitempty"`
Plivo *PlivoConfig `json:"plivo,omitempty" yaml:"plivo,omitempty"`
Pushbullet *PushbulletConfig `json:"pushbullet,omitempty" yaml:"pushbullet,omitempty"`
PushbulletSMS *PushbulletSMSConfig `json:"pushbullet_sms,omitempty" yaml:"pushbullet_sms,omitempty"`
Pushover *PushoverConfig `json:"pushover,omitempty" yaml:"pushover,omitempty"`
Reddit *RedditConfig `json:"reddit,omitempty" yaml:"reddit,omitempty"`
RocketChat *RocketChatConfig `json:"rocketchat,omitempty" yaml:"rocketchat,omitempty"`
SendGrid *SendGridConfig `json:"sendgrid,omitempty" yaml:"sendgrid,omitempty"`
Slack *SlackConfig `json:"slack,omitempty" yaml:"slack,omitempty"`
Syslog *SyslogConfig `json:"syslog,omitempty" yaml:"syslog,omitempty"`
Telegram *TelegramConfig `json:"telegram,omitempty" yaml:"telegram,omitempty"`
TextMagic *TextMagicConfig `json:"textmagic,omitempty" yaml:"textmagic,omitempty"`
Twilio *TwilioConfig `json:"twilio,omitempty" yaml:"twilio,omitempty"`
Twitter *TwitterConfig `json:"twitter,omitempty" yaml:"twitter,omitempty"`
Viber *ViberConfig `json:"viber,omitempty" yaml:"viber,omitempty"`
WebPush *WebPushConfig `json:"webpush,omitempty" yaml:"webpush,omitempty"`
WeChat *WeChatConfig `json:"wechat,omitempty" yaml:"wechat,omitempty"`
WhatsApp *WhatsAppConfig `json:"whatsapp,omitempty" yaml:"whatsapp,omitempty"`
}
// AmazonSESConfig - Amazon SES notifications.
type AmazonSESConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
AccessKeyID string `json:"access_key_id" yaml:"access_key_id"`
SecretKey string `json:"secret_key" yaml:"secret_key"`
Region string `json:"region" yaml:"region"`
SenderAddress string `json:"sender_address" yaml:"sender_address"`
Receivers []string `json:"receivers" yaml:"receivers"`
}
// AmazonSNSConfig - Amazon SNS notifications.
type AmazonSNSConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
AccessKeyID string `json:"access_key_id" yaml:"access_key_id"`
SecretKey string `json:"secret_key" yaml:"secret_key"`
Region string `json:"region" yaml:"region"`
Receivers []string `json:"receivers" yaml:"receivers"`
}
// BarkConfig - Bark notifications.
type BarkConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
DeviceKey string `json:"device_key" yaml:"device_key"`
Servers []string `json:"servers,omitempty" yaml:"servers,omitempty"`
}
// DingDingConfig - DingTalk notifications.
type DingDingConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
Token string `json:"token" yaml:"token"`
Secret string `json:"secret" yaml:"secret"`
}
// DiscordConfig - Discord notifications.
type DiscordConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
Token string `json:"token" yaml:"token"`
TokenType string `json:"token_type,omitempty" yaml:"token_type,omitempty"`
Channels []string `json:"channels" yaml:"channels"`
}
// FCMConfig - Firebase Cloud Messaging notifications.
type FCMConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
CredentialsFile string `json:"credentials_file,omitempty" yaml:"credentials_file,omitempty"`
ProjectID string `json:"project_id,omitempty" yaml:"project_id,omitempty"`
DeviceTokens []string `json:"device_tokens" yaml:"device_tokens"`
}
// GoogleChatConfig - Google Chat notifications.
type GoogleChatConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
CredentialsFile string `json:"credentials_file,omitempty" yaml:"credentials_file,omitempty"`
CredentialsJSON string `json:"credentials_json,omitempty" yaml:"credentials_json,omitempty"`
Spaces []string `json:"spaces" yaml:"spaces"`
}
// HTTPConfig - HTTP webhook notifications.
type HTTPConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
URLs []string `json:"urls,omitempty" yaml:"urls,omitempty"`
Webhooks []HTTPWebhookConfig `json:"webhooks,omitempty" yaml:"webhooks,omitempty"`
}
// HTTPWebhookConfig - Detailed webhook configuration.
type HTTPWebhookConfig struct {
URL string `json:"url" yaml:"url"`
Method string `json:"method,omitempty" yaml:"method,omitempty"`
ContentType string `json:"content_type,omitempty" yaml:"content_type,omitempty"`
Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"`
}
// LarkConfig - Lark notifications.
type LarkConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
Webhook *LarkWebhookConfig `json:"webhook,omitempty" yaml:"webhook,omitempty"`
CustomApp *LarkCustomAppConfig `json:"custom_app,omitempty" yaml:"custom_app,omitempty"`
}
// LarkWebhookConfig - Lark webhook notifications.
type LarkWebhookConfig struct {
URL string `json:"url" yaml:"url"`
}
// LarkCustomAppConfig - Lark custom app notifications.
type LarkCustomAppConfig struct {
AppID string `json:"app_id" yaml:"app_id"`
AppSecret string `json:"app_secret" yaml:"app_secret"`
Receivers []LarkReceiverConfig `json:"receivers" yaml:"receivers"`
}
// LarkReceiverConfig - Lark receiver IDs.
type LarkReceiverConfig struct {
Type string `json:"type" yaml:"type"`
ID string `json:"id" yaml:"id"`
}
// LineConfig - Line bot notifications.
type LineConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
ChannelSecret string `json:"channel_secret" yaml:"channel_secret"`
ChannelAccessToken string `json:"channel_access_token" yaml:"channel_access_token"`
Receivers []string `json:"receivers" yaml:"receivers"`
}
// LineNotifyConfig - Line notify token notifications.
type LineNotifyConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
Receivers []string `json:"receivers" yaml:"receivers"`
}
// MailConfig - SMTP mail notifications.
type MailConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
SenderAddress string `json:"sender_address" yaml:"sender_address"`
SMTPHost string `json:"smtp_host" yaml:"smtp_host"`
SMTPIdentity string `json:"smtp_identity,omitempty" yaml:"smtp_identity,omitempty"`
SMTPUsername string `json:"smtp_username,omitempty" yaml:"smtp_username,omitempty"`
SMTPPassword string `json:"smtp_password,omitempty" yaml:"smtp_password,omitempty"`
SMTPAuthHost string `json:"smtp_auth_host,omitempty" yaml:"smtp_auth_host,omitempty"`
BodyType string `json:"body_type,omitempty" yaml:"body_type,omitempty"`
Receivers []string `json:"receivers" yaml:"receivers"`
}
// MailgunConfig - Mailgun notifications.
type MailgunConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
Domain string `json:"domain" yaml:"domain"`
APIKey string `json:"api_key" yaml:"api_key"`
SenderAddress string `json:"sender_address" yaml:"sender_address"`
Receivers []string `json:"receivers" yaml:"receivers"`
}
// MatrixConfig - Matrix notifications.
type MatrixConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
UserID string `json:"user_id" yaml:"user_id"`
RoomID string `json:"room_id" yaml:"room_id"`
HomeServer string `json:"home_server" yaml:"home_server"`
AccessToken string `json:"access_token" yaml:"access_token"`
}
// MattermostConfig - Mattermost notifications.
type MattermostConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
URL string `json:"url" yaml:"url"`
LoginID string `json:"login_id,omitempty" yaml:"login_id,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty"`
Channels []string `json:"channels" yaml:"channels"`
}
// MSTeamsConfig - Microsoft Teams notifications.
type MSTeamsConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
Webhooks []string `json:"webhooks" yaml:"webhooks"`
WrapText bool `json:"wrap_text,omitempty" yaml:"wrap_text,omitempty"`
DisableWebhookValidation bool `json:"disable_webhook_validation,omitempty" yaml:"disable_webhook_validation,omitempty"`
UserAgent string `json:"user_agent,omitempty" yaml:"user_agent,omitempty"`
}
// PagerDutyConfig - PagerDuty notifications.
type PagerDutyConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
Token string `json:"token" yaml:"token"`
FromAddress string `json:"from_address" yaml:"from_address"`
Receivers []string `json:"receivers" yaml:"receivers"`
NotificationType string `json:"notification_type,omitempty" yaml:"notification_type,omitempty"`
Urgency string `json:"urgency,omitempty" yaml:"urgency,omitempty"`
PriorityID string `json:"priority_id,omitempty" yaml:"priority_id,omitempty"`
}
// PlivoConfig - Plivo notifications.
type PlivoConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
AuthID string `json:"auth_id" yaml:"auth_id"`
AuthToken string `json:"auth_token" yaml:"auth_token"`
Source string `json:"source" yaml:"source"`
CallbackURL string `json:"callback_url,omitempty" yaml:"callback_url,omitempty"`
CallbackMethod string `json:"callback_method,omitempty" yaml:"callback_method,omitempty"`
Receivers []string `json:"receivers" yaml:"receivers"`
}
// PushbulletConfig - Pushbullet notifications.
type PushbulletConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
APIToken string `json:"api_token" yaml:"api_token"`
DeviceNicknames []string `json:"device_nicknames" yaml:"device_nicknames"`
}
// PushbulletSMSConfig - Pushbullet SMS notifications.
type PushbulletSMSConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
APIToken string `json:"api_token" yaml:"api_token"`
DeviceNickname string `json:"device_nickname" yaml:"device_nickname"`
PhoneNumbers []string `json:"phone_numbers" yaml:"phone_numbers"`
}
// PushoverConfig - Pushover notifications.
type PushoverConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
AppToken string `json:"app_token" yaml:"app_token"`
Recipients []string `json:"recipients" yaml:"recipients"`
}
// RedditConfig - Reddit notifications.
type RedditConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
ClientID string `json:"client_id" yaml:"client_id"`
ClientSecret string `json:"client_secret" yaml:"client_secret"`
Username string `json:"username" yaml:"username"`
Password string `json:"password" yaml:"password"`
Recipients []string `json:"recipients" yaml:"recipients"`
}
// RocketChatConfig - Rocket.Chat notifications.
type RocketChatConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
ServerURL string `json:"server_url" yaml:"server_url"`
Scheme string `json:"scheme" yaml:"scheme"`
UserID string `json:"user_id" yaml:"user_id"`
Token string `json:"token" yaml:"token"`
Channels []string `json:"channels" yaml:"channels"`
}
// SendGridConfig - SendGrid notifications.
type SendGridConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
APIKey string `json:"api_key" yaml:"api_key"`
SenderAddress string `json:"sender_address" yaml:"sender_address"`
SenderName string `json:"sender_name" yaml:"sender_name"`
Receivers []string `json:"receivers" yaml:"receivers"`
}
// SlackConfig - Slack notifications.
type SlackConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
APIToken string `json:"api_token" yaml:"api_token"`
Channels []string `json:"channels" yaml:"channels"`
}
// SyslogConfig - Syslog notifications.
type SyslogConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
Priority string `json:"priority,omitempty" yaml:"priority,omitempty"`
Network string `json:"network,omitempty" yaml:"network,omitempty"`
Address string `json:"address,omitempty" yaml:"address,omitempty"`
Tag string `json:"tag,omitempty" yaml:"tag,omitempty"`
}
// TelegramConfig - Telegram notifications.
type TelegramConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
APIToken string `json:"api_token" yaml:"api_token"`
ChatIDs []string `json:"chat_ids" yaml:"chat_ids"`
ParseMode string `json:"parse_mode,omitempty" yaml:"parse_mode,omitempty"`
}
// TextMagicConfig - TextMagic notifications.
type TextMagicConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
Username string `json:"username" yaml:"username"`
APIKey string `json:"api_key" yaml:"api_key"`
PhoneNumbers []string `json:"phone_numbers" yaml:"phone_numbers"`
}
// TwilioConfig - Twilio notifications.
type TwilioConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
AccountSID string `json:"account_sid" yaml:"account_sid"`
AuthToken string `json:"auth_token" yaml:"auth_token"`
FromNumber string `json:"from_number" yaml:"from_number"`
PhoneNumbers []string `json:"phone_numbers" yaml:"phone_numbers"`
}
// TwitterConfig - Twitter notifications.
type TwitterConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
ConsumerKey string `json:"consumer_key" yaml:"consumer_key"`
ConsumerSecret string `json:"consumer_secret" yaml:"consumer_secret"`
AccessToken string `json:"access_token" yaml:"access_token"`
AccessTokenSecret string `json:"access_token_secret" yaml:"access_token_secret"`
Recipients []string `json:"recipients" yaml:"recipients"`
}
// ViberConfig - Viber notifications.
type ViberConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
AppKey string `json:"app_key" yaml:"app_key"`
SenderName string `json:"sender_name" yaml:"sender_name"`
SenderAvatar string `json:"sender_avatar,omitempty" yaml:"sender_avatar,omitempty"`
Receivers []string `json:"receivers" yaml:"receivers"`
WebhookURL string `json:"webhook_url,omitempty" yaml:"webhook_url,omitempty"`
}
// WebPushConfig - Webpush notifications.
type WebPushConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
VAPIDPublicKey string `json:"vapid_public_key" yaml:"vapid_public_key"`
VAPIDPrivateKey string `json:"vapid_private_key" yaml:"vapid_private_key"`
Subscriptions []WebPushSubscriptionConfig `json:"subscriptions" yaml:"subscriptions"`
}
// WebPushSubscriptionConfig - Webpush subscription data.
type WebPushSubscriptionConfig struct {
Endpoint string `json:"endpoint" yaml:"endpoint"`
Keys WebPushKeysConfig `json:"keys" yaml:"keys"`
}
// WebPushKeysConfig - Webpush subscription keys.
type WebPushKeysConfig struct {
Auth string `json:"auth" yaml:"auth"`
P256DH string `json:"p256dh" yaml:"p256dh"`
}
// WeChatConfig - WeChat notifications.
type WeChatConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
AppID string `json:"app_id" yaml:"app_id"`
AppSecret string `json:"app_secret" yaml:"app_secret"`
Token string `json:"token" yaml:"token"`
EncodingAESKey string `json:"encoding_aes_key" yaml:"encoding_aes_key"`
Receivers []string `json:"receivers" yaml:"receivers"`
}
// WhatsAppConfig - WhatsApp notifications.
type WhatsAppConfig struct {
NotificationServiceConfig `json:",inline" yaml:",inline"`
Receivers []string `json:"receivers,omitempty" yaml:"receivers,omitempty"`
}
func defaultNotificationsConfig() *NotificationsConfig {
return &NotificationsConfig{
Enabled: false,
Events: []string{},
Templates: map[string]*NotificationTemplateConfig{},
Services: &NotificationsServicesConfig{},
}
}
+135 -65
View File
@@ -24,20 +24,21 @@ import (
"path/filepath"
"github.com/bishopfox/sliver/server/assets"
"github.com/bishopfox/sliver/server/db/models"
"github.com/bishopfox/sliver/server/log"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
const (
serverConfigFileName = "server.json"
serverConfigFileName = "server.yaml"
serverLegacyConfigFileName = "server.json"
)
var (
serverConfigLog = log.NamedLogger("config", "server")
)
// GetServerConfigPath - File path to config.json
// GetServerConfigPath - File path to config.yaml
func GetServerConfigPath() string {
appDir := assets.GetRootAppDir()
serverConfigPath := filepath.Join(appDir, "configs", serverConfigFileName)
@@ -45,104 +46,151 @@ func GetServerConfigPath() string {
return serverConfigPath
}
func getServerLegacyConfigPath() string {
appDir := assets.GetRootAppDir()
return filepath.Join(appDir, "configs", serverLegacyConfigFileName)
}
// LogConfig - Server logging config
type LogConfig struct {
Level int `json:"level"`
GRPCUnaryPayloads bool `json:"grpc_unary_payloads"`
GRPCStreamPayloads bool `json:"grpc_stream_payloads"`
TLSKeyLogger bool `json:"tls_key_logger"`
Level int `json:"level" yaml:"level"`
GRPCUnaryPayloads bool `json:"grpc_unary_payloads" yaml:"grpc_unary_payloads"`
GRPCStreamPayloads bool `json:"grpc_stream_payloads" yaml:"grpc_stream_payloads"`
TLSKeyLogger bool `json:"tls_key_logger" yaml:"tls_key_logger"`
}
// DaemonConfig - Configure daemon mode
type DaemonConfig struct {
Host string `json:"host"`
Port int `json:"port"`
Tailscale bool `json:"tailscale"`
Host string `json:"host" yaml:"host"`
Port int `json:"port" yaml:"port"`
Tailscale bool `json:"tailscale" yaml:"tailscale"`
}
// JobConfig - Restart Jobs on Load
type JobConfig struct {
Multiplayer []*MultiplayerJobConfig `json:"multiplayer"`
MTLS []*MTLSJobConfig `json:"mtls,omitempty"`
WG []*WGJobConfig `json:"wg,omitempty"`
DNS []*DNSJobConfig `json:"dns,omitempty"`
HTTP []*HTTPJobConfig `json:"http,omitempty"`
Multiplayer []*MultiplayerJobConfig `json:"multiplayer" yaml:"multiplayer"`
MTLS []*MTLSJobConfig `json:"mtls,omitempty" yaml:"mtls,omitempty"`
WG []*WGJobConfig `json:"wg,omitempty" yaml:"wg,omitempty"`
DNS []*DNSJobConfig `json:"dns,omitempty" yaml:"dns,omitempty"`
HTTP []*HTTPJobConfig `json:"http,omitempty" yaml:"http,omitempty"`
}
type MultiplayerJobConfig struct {
Host string `json:"host"`
Port uint16 `json:"port"`
JobID string `json:"job_id"`
Tailscale bool `json:"tailscale"`
Host string `json:"host" yaml:"host"`
Port uint16 `json:"port" yaml:"port"`
JobID string `json:"job_id" yaml:"job_id"`
Tailscale bool `json:"tailscale" yaml:"tailscale"`
}
// MTLSJobConfig - Per-type job configs
type MTLSJobConfig struct {
Host string `json:"host"`
Port uint16 `json:"port"`
JobID string `json:"job_id"`
Host string `json:"host" yaml:"host"`
Port uint16 `json:"port" yaml:"port"`
JobID string `json:"job_id" yaml:"job_id"`
}
// WGJobConfig - Per-type job configs
type WGJobConfig struct {
Port uint16 `json:"port"`
NPort uint16 `json:"nport"`
KeyPort uint16 `json:"key_port"`
JobID string `json:"job_id"`
Port uint16 `json:"port" yaml:"port"`
NPort uint16 `json:"nport" yaml:"nport"`
KeyPort uint16 `json:"key_port" yaml:"key_port"`
JobID string `json:"job_id" yaml:"job_id"`
}
// DNSJobConfig - Persistent DNS job config
type DNSJobConfig struct {
Domains []string `json:"domains"`
Canaries bool `json:"canaries"`
Host string `json:"host"`
Port uint16 `json:"port"`
JobID string `json:"job_id"`
EnforceOTP bool `json:"enforce_otp"`
Domains []string `json:"domains" yaml:"domains"`
Canaries bool `json:"canaries" yaml:"canaries"`
Host string `json:"host" yaml:"host"`
Port uint16 `json:"port" yaml:"port"`
JobID string `json:"job_id" yaml:"job_id"`
EnforceOTP bool `json:"enforce_otp" yaml:"enforce_otp"`
}
// HTTPJobConfig - Persistent HTTP job config
type HTTPJobConfig struct {
Domain string `json:"domain"`
Host string `json:"host"`
Port uint16 `json:"port"`
Secure bool `json:"secure"`
Website string `json:"website"`
Cert []byte `json:"cert"`
Key []byte `json:"key"`
ACME bool `json:"acme"`
JobID string `json:"job_id"`
EnforceOTP bool `json:"enforce_otp"`
LongPollTimeout int64 `json:"long_poll_timeout"`
LongPollJitter int64 `json:"long_poll_jitter"`
RandomizeJARM bool `json:"randomize_jarm"`
Domain string `json:"domain" yaml:"domain"`
Host string `json:"host" yaml:"host"`
Port uint16 `json:"port" yaml:"port"`
Secure bool `json:"secure" yaml:"secure"`
Website string `json:"website" yaml:"website"`
Cert []byte `json:"cert" yaml:"cert"`
Key []byte `json:"key" yaml:"key"`
ACME bool `json:"acme" yaml:"acme"`
JobID string `json:"job_id" yaml:"job_id"`
EnforceOTP bool `json:"enforce_otp" yaml:"enforce_otp"`
LongPollTimeout int64 `json:"long_poll_timeout" yaml:"long_poll_timeout"`
LongPollJitter int64 `json:"long_poll_jitter" yaml:"long_poll_jitter"`
RandomizeJARM bool `json:"randomize_jarm" yaml:"randomize_jarm"`
}
// WatchTowerConfig - Watch Tower job config
type WatchTowerConfig struct {
VTApiKey string `json:"vt_api_key"`
XForceApiKey string `json:"xforce_api_key"`
XForceApiPassword string `json:"xforce_api_password"`
VTApiKey string `json:"vt_api_key" yaml:"vt_api_key"`
XForceApiKey string `json:"xforce_api_key" yaml:"xforce_api_key"`
XForceApiPassword string `json:"xforce_api_password" yaml:"xforce_api_password"`
}
// http server defaults for anonymous requests
type HttpDefaultHeader struct {
Method string `json:"method" yaml:"method"`
Name string `json:"name" yaml:"name"`
Value string `json:"value" yaml:"value"`
Probability int32 `json:"probability" yaml:"probability"`
}
type httpDefaultHeaderLegacy struct {
Method string `json:"method" yaml:"method"`
Name string `json:"name" yaml:"name"`
Value string `json:"value" yaml:"value"`
Probability int32 `json:"probability" yaml:"probability"`
ID string `json:"id" yaml:"id"`
HttpC2ServerConfigID *string `json:"httpc2serverconfigid" yaml:"httpc2serverconfigid"`
HttpC2ImplantConfigID *string `json:"httpc2implantconfigid" yaml:"httpc2implantconfigid"`
}
func (h *HttpDefaultHeader) UnmarshalJSON(data []byte) error {
var raw httpDefaultHeaderLegacy
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
h.Method = raw.Method
h.Name = raw.Name
h.Value = raw.Value
h.Probability = raw.Probability
return nil
}
func (h *HttpDefaultHeader) UnmarshalYAML(node *yaml.Node) error {
var raw httpDefaultHeaderLegacy
if err := node.Decode(&raw); err != nil {
return err
}
h.Method = raw.Method
h.Name = raw.Name
h.Value = raw.Value
h.Probability = raw.Probability
return nil
}
type HttpDefaultConfig struct {
Headers []models.HttpC2Header `json:"headers"`
Headers []HttpDefaultHeader `json:"headers" yaml:"headers"`
}
// ServerConfig - Server config
type ServerConfig struct {
DaemonMode bool `json:"daemon_mode"`
DaemonConfig *DaemonConfig `json:"daemon"`
Logs *LogConfig `json:"logs"`
Watchtower *WatchTowerConfig `json:"watch_tower"`
GoProxy string `json:"go_proxy"`
HTTPDefaults *HttpDefaultConfig `json:"http_default"`
DonutBypass int `json:"donut_bypass"` // 1=skip, 2=abort on fail, 3=continue on fail.
DaemonMode bool `json:"daemon_mode" yaml:"daemon_mode"`
DaemonConfig *DaemonConfig `json:"daemon" yaml:"daemon"`
Logs *LogConfig `json:"logs" yaml:"logs"`
Watchtower *WatchTowerConfig `json:"watch_tower" yaml:"watch_tower"`
GoProxy string `json:"go_proxy" yaml:"go_proxy"`
HTTPDefaults *HttpDefaultConfig `json:"http_default" yaml:"http_default"`
DonutBypass int `json:"donut_bypass" yaml:"donut_bypass"` // 1=skip, 2=abort on fail, 3=continue on fail.
Notifications *NotificationsConfig `json:"notifications" yaml:"notifications"`
// 'GOOS/GOARCH' -> CC path
CC map[string]string `json:"cc"`
CXX map[string]string `json:"cxx"`
CC map[string]string `json:"cc" yaml:"cc"`
CXX map[string]string `json:"cxx" yaml:"cxx"`
}
// Save - Save config file to disk
@@ -156,7 +204,7 @@ func (c *ServerConfig) Save() error {
return err
}
}
data, err := json.MarshalIndent(c, "", " ")
data, err := yaml.Marshal(c)
if err != nil {
return err
}
@@ -171,18 +219,33 @@ func (c *ServerConfig) Save() error {
// GetServerConfig - Get config value
func GetServerConfig() *ServerConfig {
configPath := GetServerConfigPath()
legacyPath := getServerLegacyConfigPath()
config := getDefaultServerConfig()
migratedLegacy := false
if _, err := os.Stat(configPath); !os.IsNotExist(err) {
data, err := os.ReadFile(configPath)
if err != nil {
serverConfigLog.Errorf("Failed to read config file %s", err)
return config
}
err = json.Unmarshal(data, config)
err = yaml.Unmarshal(data, config)
if err != nil {
serverConfigLog.Errorf("Failed to parse config file %s", err)
return config
}
} else if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
data, err := os.ReadFile(legacyPath)
if err != nil {
serverConfigLog.Errorf("Failed to read legacy config file %s", err)
return config
}
err = json.Unmarshal(data, config)
if err != nil {
serverConfigLog.Errorf("Failed to parse legacy config file %s", err)
return config
}
migratedLegacy = true
serverConfigLog.Infof("Migrating legacy config %s to %s", legacyPath, configPath)
} else {
serverConfigLog.Warnf("Config file does not exist, using defaults")
}
@@ -202,6 +265,12 @@ func GetServerConfig() *ServerConfig {
err := config.Save() // This updates the config with any missing fields
if err != nil {
serverConfigLog.Errorf("Failed to save default config %s", err)
return config
}
if migratedLegacy {
if err := renameLegacyConfig(legacyPath); err != nil {
serverConfigLog.Errorf("Failed to rename legacy config %s", err)
}
}
return config
}
@@ -219,8 +288,8 @@ func getDefaultServerConfig() *ServerConfig {
GRPCStreamPayloads: false,
},
HTTPDefaults: &HttpDefaultConfig{
Headers: []models.HttpC2Header{
models.HttpC2Header{
Headers: []HttpDefaultHeader{
{
Method: "GET",
Name: "Cache-Control",
Value: "no-store, no-cache, must-revalidate",
@@ -228,8 +297,9 @@ func getDefaultServerConfig() *ServerConfig {
},
},
},
DonutBypass: 3,
CC: map[string]string{},
CXX: map[string]string{},
DonutBypass: 3,
Notifications: defaultNotificationsConfig(),
CC: map[string]string{},
CXX: map[string]string{},
}
}
+230
View File
@@ -0,0 +1,230 @@
package configs
import (
"encoding/json"
"os"
"path/filepath"
"testing"
)
func TestServerConfigParsesYAML(t *testing.T) {
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
configPath := GetServerConfigPath()
if err := os.MkdirAll(filepath.Dir(configPath), 0700); err != nil {
t.Fatalf("failed to create config dir: %v", err)
}
data := []byte(`daemon_mode: true
daemon:
host: "127.0.0.1"
port: 4444
tailscale: true
logs:
level: 5
grpc_unary_payloads: true
grpc_stream_payloads: true
tls_key_logger: true
watch_tower:
vt_api_key: "vt"
xforce_api_key: "xforce"
xforce_api_password: "secret"
go_proxy: "https://proxy.example"
http_default:
headers:
- method: "POST"
name: "X-Test"
value: "abc"
probability: 50
donut_bypass: 2
notifications:
enabled: true
events:
- session-connected
services:
slack:
enabled: true
api_token: "slack-token"
channels:
- "C123"
templates:
session-connected:
type: "text"
template: "session.tmpl"
cc:
linux/amd64: "/usr/bin/cc"
cxx:
linux/amd64: "/usr/bin/c++"
`)
if err := os.WriteFile(configPath, data, 0600); err != nil {
t.Fatalf("failed to write yaml config: %v", err)
}
config := GetServerConfig()
if !config.DaemonMode {
t.Fatalf("expected daemon_mode true")
}
if config.DaemonConfig == nil || config.DaemonConfig.Port != 4444 {
t.Fatalf("expected daemon port %d, got %v", 4444, config.DaemonConfig)
}
if config.Logs == nil || config.Logs.Level != 5 {
t.Fatalf("expected logs level %d, got %v", 5, config.Logs)
}
if config.Logs == nil || !config.Logs.TLSKeyLogger {
t.Fatalf("expected tls_key_logger true")
}
if config.Watchtower == nil || config.Watchtower.VTApiKey != "vt" {
t.Fatalf("expected watch_tower vt_api_key %q, got %v", "vt", config.Watchtower)
}
if config.GoProxy != "https://proxy.example" {
t.Fatalf("expected go_proxy %q, got %q", "https://proxy.example", config.GoProxy)
}
if config.HTTPDefaults == nil || len(config.HTTPDefaults.Headers) != 1 {
t.Fatalf("expected 1 http default header, got %v", config.HTTPDefaults)
}
header := config.HTTPDefaults.Headers[0]
if header.Method != "POST" || header.Name != "X-Test" || header.Value != "abc" || header.Probability != 50 {
t.Fatalf("unexpected header values: %#v", header)
}
if config.DonutBypass != 2 {
t.Fatalf("expected donut_bypass %d, got %d", 2, config.DonutBypass)
}
if config.Notifications == nil || !config.Notifications.Enabled {
t.Fatalf("expected notifications enabled")
}
if len(config.Notifications.Events) != 1 || config.Notifications.Events[0] != "session-connected" {
t.Fatalf("unexpected notifications events: %v", config.Notifications.Events)
}
if config.Notifications.Services == nil || config.Notifications.Services.Slack == nil {
t.Fatalf("expected slack notifications config")
}
if !config.Notifications.Services.Slack.Enabled || config.Notifications.Services.Slack.APIToken != "slack-token" {
t.Fatalf("unexpected slack notifications config: %#v", config.Notifications.Services.Slack)
}
if len(config.Notifications.Services.Slack.Channels) != 1 || config.Notifications.Services.Slack.Channels[0] != "C123" {
t.Fatalf("unexpected slack channels: %v", config.Notifications.Services.Slack.Channels)
}
if config.Notifications.Templates == nil {
t.Fatalf("expected notifications templates config")
}
tmpl := config.Notifications.Templates["session-connected"]
if tmpl == nil || tmpl.Type != "text" || tmpl.Template != "session.tmpl" {
t.Fatalf("unexpected template config: %#v", tmpl)
}
if config.CC["linux/amd64"] != "/usr/bin/cc" {
t.Fatalf("expected cc override %q, got %q", "/usr/bin/cc", config.CC["linux/amd64"])
}
if config.CXX["linux/amd64"] != "/usr/bin/c++" {
t.Fatalf("expected cxx override %q, got %q", "/usr/bin/c++", config.CXX["linux/amd64"])
}
}
func TestServerConfigWritesDefault(t *testing.T) {
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
config := GetServerConfig()
if config.DaemonConfig == nil {
t.Fatalf("expected default daemon config")
}
if _, err := os.Stat(GetServerConfigPath()); err != nil {
t.Fatalf("expected default config file to exist: %v", err)
}
}
func TestServerConfigMigratesLegacyJSON(t *testing.T) {
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
configDir := filepath.Dir(GetServerConfigPath())
if err := os.MkdirAll(configDir, 0700); err != nil {
t.Fatalf("failed to create config dir: %v", err)
}
legacyPath := filepath.Join(configDir, serverLegacyConfigFileName)
type legacyHeader struct {
ID string `json:"id"`
HttpC2ServerConfigID *string `json:"httpc2serverconfigid"`
HttpC2ImplantConfigID *string `json:"httpc2implantconfigid"`
Method string `json:"method"`
Name string `json:"name"`
Value string `json:"value"`
Probability int32 `json:"probability"`
}
type legacyHTTPDefaults struct {
Headers []legacyHeader `json:"headers"`
}
type legacyServerConfig struct {
DaemonMode bool `json:"daemon_mode"`
DaemonConfig *DaemonConfig `json:"daemon"`
Logs *LogConfig `json:"logs"`
Watchtower *WatchTowerConfig `json:"watch_tower"`
GoProxy string `json:"go_proxy"`
HTTPDefaults *legacyHTTPDefaults `json:"http_default"`
DonutBypass int `json:"donut_bypass"`
CC map[string]string `json:"cc"`
CXX map[string]string `json:"cxx"`
}
legacy := &legacyServerConfig{
DaemonMode: true,
DaemonConfig: &DaemonConfig{
Host: "127.0.0.1",
Port: 31338,
Tailscale: false,
},
Logs: &LogConfig{
Level: 4,
GRPCUnaryPayloads: true,
GRPCStreamPayloads: true,
TLSKeyLogger: true,
},
Watchtower: &WatchTowerConfig{
VTApiKey: "vt-legacy",
XForceApiKey: "xforce-legacy",
XForceApiPassword: "secret",
},
GoProxy: "https://proxy.legacy",
HTTPDefaults: &legacyHTTPDefaults{
Headers: []legacyHeader{
{
ID: "00000000-0000-0000-0000-000000000000",
Method: "GET",
Name: "Cache-Control",
Value: "no-cache",
Probability: 42,
},
},
},
DonutBypass: 2,
CC: map[string]string{"linux/amd64": "/usr/bin/cc"},
CXX: map[string]string{"linux/amd64": "/usr/bin/c++"},
}
data, err := json.MarshalIndent(legacy, "", " ")
if err != nil {
t.Fatalf("failed to marshal legacy config: %v", err)
}
if err := os.WriteFile(legacyPath, data, 0600); err != nil {
t.Fatalf("failed to write legacy config: %v", err)
}
config := GetServerConfig()
if !config.DaemonMode || config.DaemonConfig == nil || config.DaemonConfig.Port != 31338 {
t.Fatalf("expected legacy daemon config to load")
}
if config.Watchtower == nil || config.Watchtower.VTApiKey != "vt-legacy" {
t.Fatalf("expected legacy watch_tower to load")
}
if config.GoProxy != "https://proxy.legacy" {
t.Fatalf("expected legacy go_proxy %q, got %q", "https://proxy.legacy", config.GoProxy)
}
if config.HTTPDefaults == nil || len(config.HTTPDefaults.Headers) != 1 {
t.Fatalf("expected legacy http defaults to load")
}
if _, err := os.Stat(GetServerConfigPath()); err != nil {
t.Fatalf("expected migrated yaml config file to exist: %v", err)
}
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
t.Fatalf("expected legacy config to be renamed: %v", err)
}
if _, err := os.Stat(legacyBackupPath(legacyPath)); err != nil {
t.Fatalf("expected legacy backup file to exist: %v", err)
}
}
+10
View File
@@ -1494,3 +1494,13 @@ func GetCertificateInfo(categoryOptions uint32, cn string) ([]*models.Certificat
// Processing of the data can occur at the caller. It does not need to happen here.
return certInfo, nil
}
// CertificateAuthorities - Return all certificate authorities
func CertificateAuthorities() ([]*models.CertificateAuthority, error) {
authorities := []*models.CertificateAuthority{}
err := Session().Where(&models.CertificateAuthority{}).Find(&authorities).Error
if err != nil {
return nil, err
}
return authorities, nil
}
+1
View File
@@ -8,6 +8,7 @@ Database models and ORM definitions for server state. Defines data schemas, rela
- `beacon.go` – Defines the beacon ORM model and associated relations.
- `canary.go` – Stores DNS canary records and status flags.
- `certificate_authorities.go` – Stores certificate authority PEM data for the server.
- `certificates.go` – Persists certificate metadata for listeners and operators.
- `crackstations.go` – Models cracking nodes and benchmark data.
- `credentials.go` – Holds captured credential entries and tags.
@@ -0,0 +1,47 @@
package models
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"time"
"github.com/gofrs/uuid"
"gorm.io/gorm"
)
// CertificateAuthority - Certificate authority database model
// Stored separately from leaf certificates.
type CertificateAuthority struct {
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
CreatedAt time.Time `gorm:"->;<-:create;"`
CommonName string
CAType string
CertificatePEM string
PrivateKeyPEM string
}
// BeforeCreate - GORM hook to automatically set values
func (c *CertificateAuthority) BeforeCreate(tx *gorm.DB) (err error) {
c.ID, err = uuid.NewV4()
if err != nil {
return err
}
c.CreatedAt = time.Now()
return nil
}
+1
View File
@@ -67,6 +67,7 @@ func newDBClient() *gorm.DB {
&models.CrackCommand{},
&models.CrackFile{},
&models.CrackFileChunk{},
&models.CertificateAuthority{},
&models.Certificate{},
&models.Host{},
&models.KeyValue{},
+6 -1
View File
@@ -136,6 +136,10 @@ func TestSliverSharedLibWindows(t *testing.T) {
// 386
multiLibrary(t, "windows", "386", true)
multiLibrary(t, "windows", "386", false)
// Shared Library Shellcode
multiWindowsLibraryShellcode(t, true)
multiWindowsLibraryShellcode(t, false)
}
func TestSliverExecutableLinux(t *testing.T) {
@@ -554,7 +558,8 @@ func multiWindowsLibraryShellcode(t *testing.T, debug bool) {
Debug: debug,
ObfuscateSymbols: true,
Format: clientpb.OutputFormat_SHELLCODE,
IsSharedLib: true,
IsShellcode: true,
IsSharedLib: false,
Exports: []string{"FoobarW"},
IncludeMTLS: true,
IncludeHTTP: true,
File diff suppressed because it is too large. Load diff
+113
View File
@@ -0,0 +1,113 @@
package notifications
import (
"os"
"reflect"
"strings"
"unicode"
)
func resolveEnvString(value string) (string, bool, bool) {
name, ok := parseEnvVar(value)
if !ok {
return value, false, false
}
expanded, found := os.LookupEnv(name)
if !found {
notificationsLog.Warnf("Notifications env var %q is not set", name)
return "", true, false
}
if expanded == "" {
notificationsLog.Debugf("Notifications env var %q is set but empty", name)
}
return expanded, true, true
}
func parseEnvVar(value string) (string, bool) {
if len(value) < 2 || value[0] != '$' {
return "", false
}
name := value[1:]
if strings.HasPrefix(name, "{") && strings.HasSuffix(name, "}") {
name = strings.TrimSuffix(strings.TrimPrefix(name, "{"), "}")
}
if !isValidEnvName(name) {
return "", false
}
return name, true
}
func isValidEnvName(name string) bool {
if name == "" {
return false
}
for i, r := range name {
if i == 0 {
if r != '_' && !unicode.IsLetter(r) {
return false
}
continue
}
if r != '_' && !unicode.IsLetter(r) && !unicode.IsDigit(r) {
return false
}
}
return true
}
func expandEnv(target any) {
if target == nil {
return
}
value := reflect.ValueOf(target)
if value.Kind() != reflect.Pointer {
return
}
expandEnvValue(value)
}
func expandEnvValue(value reflect.Value) {
if !value.IsValid() {
return
}
if value.Kind() == reflect.Pointer {
if value.IsNil() {
return
}
expandEnvValue(value.Elem())
return
}
switch value.Kind() {
case reflect.String:
if !value.CanSet() {
return
}
expanded, ok, _ := resolveEnvString(value.String())
if ok {
value.SetString(expanded)
}
case reflect.Struct:
for i := 0; i < value.NumField(); i++ {
expandEnvValue(value.Field(i))
}
case reflect.Slice:
for i := 0; i < value.Len(); i++ {
expandEnvValue(value.Index(i))
}
case reflect.Map:
if value.Type().Key().Kind() != reflect.String {
return
}
if value.Type().Elem().Kind() == reflect.String {
for _, key := range value.MapKeys() {
val := value.MapIndex(key)
expanded, ok, _ := resolveEnvString(val.String())
if ok {
value.SetMapIndex(key, reflect.ValueOf(expanded))
}
}
}
}
}
+75
View File
@@ -0,0 +1,75 @@
package notifications
import (
"testing"
"github.com/bishopfox/sliver/server/configs"
)
func TestResolveEnvString(t *testing.T) {
t.Setenv("SLIVER_NOTIFY_TEST", "secret")
got, ok, found := resolveEnvString("$SLIVER_NOTIFY_TEST")
if !ok || !found || got != "secret" {
t.Fatalf("expected env replacement, got %q (ok=%v)", got, ok)
}
got, ok, found = resolveEnvString("${SLIVER_NOTIFY_TEST}")
if !ok || !found || got != "secret" {
t.Fatalf("expected env replacement with braces, got %q (ok=%v)", got, ok)
}
got, ok, found = resolveEnvString("plain")
if ok || found || got != "plain" {
t.Fatalf("expected literal value, got %q (ok=%v)", got, ok)
}
got, ok, found = resolveEnvString("$SLIVER_NOTIFY_MISSING")
if !ok || found || got != "" {
t.Fatalf("expected empty string for missing env, got %q (ok=%v)", got, ok)
}
}
func TestExpandEnvConfig(t *testing.T) {
t.Setenv("SLIVER_NOTIFY_SLACK_TOKEN", "slack-secret")
t.Setenv("SLIVER_NOTIFY_SLACK_CHANNEL", "C123")
t.Setenv("SLIVER_NOTIFY_WEBHOOK", "https://example.com/hook")
t.Setenv("SLIVER_NOTIFY_HEADER", "Bearer 123")
cfg := &configs.NotificationsConfig{
Enabled: true,
Services: &configs.NotificationsServicesConfig{
Slack: &configs.SlackConfig{
NotificationServiceConfig: configs.NotificationServiceConfig{Enabled: true},
APIToken: "$SLIVER_NOTIFY_SLACK_TOKEN",
Channels: []string{"$SLIVER_NOTIFY_SLACK_CHANNEL"},
},
HTTP: &configs.HTTPConfig{
NotificationServiceConfig: configs.NotificationServiceConfig{Enabled: true},
Webhooks: []configs.HTTPWebhookConfig{
{
URL: "$SLIVER_NOTIFY_WEBHOOK",
Headers: map[string]string{
"Authorization": "$SLIVER_NOTIFY_HEADER",
},
},
},
},
},
}
expandEnv(cfg)
if cfg.Services.Slack.APIToken != "slack-secret" {
t.Fatalf("expected slack token to expand, got %q", cfg.Services.Slack.APIToken)
}
if len(cfg.Services.Slack.Channels) != 1 || cfg.Services.Slack.Channels[0] != "C123" {
t.Fatalf("unexpected slack channels: %v", cfg.Services.Slack.Channels)
}
if cfg.Services.HTTP.Webhooks[0].URL != "https://example.com/hook" {
t.Fatalf("expected webhook url to expand, got %q", cfg.Services.HTTP.Webhooks[0].URL)
}
if cfg.Services.HTTP.Webhooks[0].Headers["Authorization"] != "Bearer 123" {
t.Fatalf("expected header to expand, got %q", cfg.Services.HTTP.Webhooks[0].Headers["Authorization"])
}
}
+38
View File
@@ -0,0 +1,38 @@
package notifications
import "testing"
func TestResolveEvents(t *testing.T) {
global := []string{"a", "b"}
service := []string{"c"}
set := resolveEvents(global, nil)
if len(set) != 2 {
t.Fatalf("expected global events, got %v", set)
}
set = resolveEvents(global, service)
if len(set) != 1 {
t.Fatalf("expected service events, got %v", set)
}
if _, ok := set["c"]; !ok {
t.Fatalf("expected service event 'c' to be present")
}
}
func TestNotifierEntryAllows(t *testing.T) {
entry := notifierEntry{
events: map[string]struct{}{"event-a": {}},
}
if !entry.allows("event-a") {
t.Fatalf("expected event-a to be allowed")
}
if entry.allows("event-b") {
t.Fatalf("did not expect event-b to be allowed")
}
entry.events = nil
if !entry.allows("anything") {
t.Fatalf("expected empty filter to allow all events")
}
}
+115
View File
@@ -0,0 +1,115 @@
package notifications
import (
"fmt"
"strings"
"github.com/bishopfox/sliver/server/core"
"github.com/bishopfox/sliver/server/db/models"
)
func formatEvent(event core.Event) (string, string) {
subject := fmt.Sprintf("Sliver event: %s", event.EventType)
lines := []string{
fmt.Sprintf("Event: %s", event.EventType),
}
if event.Session != nil {
lines = append(lines, formatSession(event.Session)...)
}
if event.Beacon != nil {
lines = append(lines, formatBeacon(event.Beacon)...)
}
if event.Job != nil {
lines = append(lines, formatJob(event.Job)...)
}
if event.Client != nil {
lines = append(lines, formatClient(event.Client)...)
}
if event.Err != nil {
lines = append(lines, fmt.Sprintf("Error: %s", event.Err.Error()))
}
return subject, strings.Join(lines, "\n")
}
func formatSession(session *core.Session) []string {
lines := []string{
fmt.Sprintf("Session ID: %s", session.ID),
}
if session.Name != "" {
lines = append(lines, fmt.Sprintf("Name: %s", session.Name))
}
if session.Username != "" {
lines = append(lines, fmt.Sprintf("User: %s", session.Username))
}
if session.Hostname != "" {
lines = append(lines, fmt.Sprintf("Host: %s", session.Hostname))
}
if session.OS != "" {
lines = append(lines, fmt.Sprintf("OS: %s", session.OS))
}
if session.Connection != nil {
if session.Connection.Transport != "" {
lines = append(lines, fmt.Sprintf("Transport: %s", session.Connection.Transport))
}
if session.Connection.RemoteAddress != "" {
lines = append(lines, fmt.Sprintf("Remote: %s", session.Connection.RemoteAddress))
}
}
return lines
}
func formatBeacon(beacon *models.Beacon) []string {
lines := []string{
fmt.Sprintf("Beacon ID: %s", beacon.ID.String()),
}
if beacon.Name != "" {
lines = append(lines, fmt.Sprintf("Name: %s", beacon.Name))
}
if beacon.Hostname != "" {
lines = append(lines, fmt.Sprintf("Host: %s", beacon.Hostname))
}
if beacon.Username != "" {
lines = append(lines, fmt.Sprintf("User: %s", beacon.Username))
}
if beacon.OS != "" {
lines = append(lines, fmt.Sprintf("OS: %s", beacon.OS))
}
if beacon.Transport != "" {
lines = append(lines, fmt.Sprintf("Transport: %s", beacon.Transport))
}
if beacon.RemoteAddress != "" {
lines = append(lines, fmt.Sprintf("Remote: %s", beacon.RemoteAddress))
}
return lines
}
func formatJob(job *core.Job) []string {
lines := []string{
fmt.Sprintf("Job ID: %d", job.ID),
}
if job.Name != "" {
lines = append(lines, fmt.Sprintf("Name: %s", job.Name))
}
if job.Protocol != "" {
lines = append(lines, fmt.Sprintf("Protocol: %s", job.Protocol))
}
if job.Port != 0 {
lines = append(lines, fmt.Sprintf("Port: %d", job.Port))
}
if len(job.Domains) > 0 {
lines = append(lines, fmt.Sprintf("Domains: %s", strings.Join(job.Domains, ", ")))
}
return lines
}
func formatClient(client *core.Client) []string {
lines := []string{
fmt.Sprintf("Client ID: %d", client.ID),
}
if client.Operator != nil && client.Operator.Name != "" {
lines = append(lines, fmt.Sprintf("Operator: %s", client.Operator.Name))
}
return lines
}
+120
View File
@@ -0,0 +1,120 @@
package notifications
import (
"context"
"time"
"github.com/bishopfox/sliver/server/core"
"github.com/bishopfox/sliver/server/log"
)
const (
notificationQueueSize = 100
notificationTimeout = 10 * time.Second
)
var (
notificationsLog = log.NamedLogger("notifications", "manager")
)
type Manager struct {
enabled bool
entries []notifierEntry
templates map[string]templateSpec
renderer *templateRenderer
queue chan core.Event
cancel context.CancelFunc
started bool
}
func (m *Manager) Start() {
if m == nil || m.started || !m.enabled || len(m.entries) == 0 {
if m != nil && !m.enabled {
notificationsLog.Infof("Notifications disabled")
}
return
}
m.started = true
m.queue = make(chan core.Event, notificationQueueSize)
notificationsLog.Infof("Starting notifications with %d service(s)", len(m.entries))
ctx, cancel := context.WithCancel(context.Background())
m.cancel = cancel
events := core.EventBroker.Subscribe()
go func() {
defer core.EventBroker.Unsubscribe(events)
for {
select {
case <-ctx.Done():
return
case event, ok := <-events:
if !ok {
return
}
select {
case m.queue <- event:
default:
notificationsLog.Warnf("Dropping notification event %q (queue full)", event.EventType)
}
}
}
}()
go func() {
for {
select {
case <-ctx.Done():
return
case event := <-m.queue:
m.dispatch(ctx, event)
}
}
}()
}
func (m *Manager) Stop() {
if m == nil || !m.started {
return
}
if m.cancel != nil {
m.cancel()
}
m.started = false
notificationsLog.Infof("Notifications stopped")
}
func (m *Manager) dispatch(ctx context.Context, event core.Event) {
defer func() {
if recovered := recover(); recovered != nil {
notificationsLog.Errorf("Notification dispatch panic for event %q: %v", event.EventType, recovered)
}
}()
subject, message := formatEvent(event)
if m.renderer != nil && len(m.templates) > 0 {
if spec, ok := m.templates[event.EventType]; ok {
rendered, err := m.renderer.render(spec, buildTemplateData(event, subject, message))
if err != nil {
notificationsLog.Warnf("Failed to render template %q for event %q: %v", spec.name, event.EventType, err)
} else {
message = rendered
notificationsLog.Debugf("Rendered %s template %q for event %q", spec.typ, spec.name, event.EventType)
}
}
}
for _, entry := range m.entries {
if !entry.allows(event.EventType) {
notificationsLog.Debugf("Skipping notification %s for event %q (filtered)", entry.name, event.EventType)
continue
}
sendCtx, cancel := context.WithTimeout(ctx, notificationTimeout)
if err := entry.notifier.Send(sendCtx, subject, message); err != nil {
notificationsLog.Warnf("Notification %s failed: %v", entry.name, err)
} else {
notificationsLog.Debugf("Notification %s delivered for event %q", entry.name, event.EventType)
}
cancel()
}
}
+58
View File
@@ -0,0 +1,58 @@
//go:build !windows
package notifications
import (
"errors"
"log/syslog"
"strconv"
"strings"
"github.com/bishopfox/sliver/server/configs"
"github.com/nikoksr/notify"
notifysyslog "github.com/nikoksr/notify/service/syslog"
)
func buildSyslog(cfg *configs.SyslogConfig) (notify.Notifier, error) {
if cfg == nil {
return nil, errors.New("syslog config is nil")
}
priority := parseSyslogPriority(cfg.Priority)
network := strings.TrimSpace(cfg.Network)
address := strings.TrimSpace(cfg.Address)
tag := strings.TrimSpace(cfg.Tag)
if network != "" || address != "" {
return notifysyslog.NewFromDial(network, address, priority, tag)
}
return notifysyslog.New(priority, tag)
}
func parseSyslogPriority(value string) syslog.Priority {
value = strings.TrimSpace(strings.ToLower(value))
if value == "" {
return syslog.LOG_INFO
}
switch value {
case "emerg", "emergency":
return syslog.LOG_EMERG
case "alert":
return syslog.LOG_ALERT
case "crit", "critical":
return syslog.LOG_CRIT
case "err", "error":
return syslog.LOG_ERR
case "warning", "warn":
return syslog.LOG_WARNING
case "notice":
return syslog.LOG_NOTICE
case "info":
return syslog.LOG_INFO
case "debug":
return syslog.LOG_DEBUG
default:
if numeric, err := strconv.Atoi(value); err == nil {
return syslog.Priority(numeric)
}
}
return syslog.LOG_INFO
}
+14
View File
@@ -0,0 +1,14 @@
//go:build windows
package notifications
import (
"errors"
"github.com/bishopfox/sliver/server/configs"
"github.com/nikoksr/notify"
)
func buildSyslog(_ *configs.SyslogConfig) (notify.Notifier, error) {
return nil, errors.New("syslog notifications are not supported on windows")
}
+211
View File
@@ -0,0 +1,211 @@
package notifications
import (
"bytes"
"errors"
"fmt"
htmltemplate "html/template"
"io"
"os"
"path/filepath"
"strings"
"sync"
texttemplate "text/template"
"time"
"github.com/bishopfox/sliver/server/assets"
"github.com/bishopfox/sliver/server/core"
"github.com/bishopfox/sliver/server/db/models"
)
const (
templateTypeText = "text"
templateTypeHTML = "html"
)
type templateSpec struct {
name string
typ string
}
type templateRenderer struct {
baseDir string
textTemplates map[string]*texttemplate.Template
htmlTemplates map[string]*htmltemplate.Template
mutex sync.RWMutex
}
type templateData struct {
EventType string
Session *core.Session
Beacon *models.Beacon
Job *core.Job
Client *core.Client
Error string
DefaultSubject string
DefaultMessage string
Timestamp time.Time
}
func templatesDir() string {
return filepath.Join(assets.GetRootAppDir(), "notifications", "templates")
}
func ensureTemplatesDir() (string, error) {
dir := templatesDir()
if err := os.MkdirAll(dir, 0700); err != nil {
return "", err
}
return dir, nil
}
func newTemplateRenderer(baseDir string) *templateRenderer {
return &templateRenderer{
baseDir: baseDir,
textTemplates: map[string]*texttemplate.Template{},
htmlTemplates: map[string]*htmltemplate.Template{},
}
}
func (r *templateRenderer) render(spec templateSpec, data templateData) (rendered string, err error) {
if spec.name == "" {
return "", errors.New("template name is empty")
}
defer func() {
if recovered := recover(); recovered != nil {
notificationsLog.Errorf("Template render panic for %q: %v", spec.name, recovered)
err = fmt.Errorf("template render panic: %v", recovered)
}
}()
exec, err := r.getTemplate(spec)
if err != nil {
return "", err
}
var buf bytes.Buffer
if err := exec.Execute(&buf, data); err != nil {
return "", err
}
return buf.String(), nil
}
type templateExecutor interface {
Execute(io.Writer, any) error
}
func (r *templateRenderer) getTemplate(spec templateSpec) (templateExecutor, error) {
switch spec.typ {
case templateTypeHTML:
return r.getHTMLTemplate(spec.name)
default:
return r.getTextTemplate(spec.name)
}
}
func (r *templateRenderer) getTextTemplate(name string) (templateExecutor, error) {
r.mutex.RLock()
if tmpl, ok := r.textTemplates[name]; ok {
r.mutex.RUnlock()
return tmpl, nil
}
r.mutex.RUnlock()
path, err := resolveTemplatePath(r.baseDir, name)
if err != nil {
return nil, err
}
content, err := os.ReadFile(path)
if err != nil {
return nil, err
}
tmpl, err := texttemplate.New(name).Parse(string(content))
if err != nil {
return nil, err
}
r.mutex.Lock()
r.textTemplates[name] = tmpl
r.mutex.Unlock()
return tmpl, nil
}
func (r *templateRenderer) getHTMLTemplate(name string) (templateExecutor, error) {
r.mutex.RLock()
if tmpl, ok := r.htmlTemplates[name]; ok {
r.mutex.RUnlock()
return tmpl, nil
}
r.mutex.RUnlock()
path, err := resolveTemplatePath(r.baseDir, name)
if err != nil {
return nil, err
}
content, err := os.ReadFile(path)
if err != nil {
return nil, err
}
tmpl, err := htmltemplate.New(name).Parse(string(content))
if err != nil {
return nil, err
}
r.mutex.Lock()
r.htmlTemplates[name] = tmpl
r.mutex.Unlock()
return tmpl, nil
}
func resolveTemplatePath(baseDir, name string) (string, error) {
trimmed := strings.TrimSpace(name)
if trimmed == "" {
return "", errors.New("template name is empty")
}
if strings.Contains(trimmed, "..") {
return "", errors.New("template name contains invalid path segments")
}
if strings.ContainsAny(trimmed, `/\\`) {
return "", errors.New("template name must not contain path separators")
}
cleanBase := filepath.Clean(baseDir)
path := filepath.Join(cleanBase, filepath.Base(trimmed))
cleanPath := filepath.Clean(path)
if !strings.HasPrefix(cleanPath, cleanBase+string(filepath.Separator)) && cleanPath != cleanBase {
return "", errors.New("template path escapes template directory")
}
return cleanPath, nil
}
func buildTemplateData(event core.Event, subject, message string) templateData {
var errText string
if event.Err != nil {
errText = event.Err.Error()
}
return templateData{
EventType: event.EventType,
Session: event.Session,
Beacon: event.Beacon,
Job: event.Job,
Client: event.Client,
Error: errText,
DefaultSubject: subject,
DefaultMessage: message,
Timestamp: time.Now().UTC(),
}
}
func parseTemplateType(value string) (string, bool) {
trimmed := strings.ToLower(strings.TrimSpace(value))
if trimmed == "" {
return templateTypeText, true
}
switch trimmed {
case "text", "plain":
return templateTypeText, true
case "html":
return templateTypeHTML, true
default:
return templateTypeText, false
}
}
+91 -22
View File
@@ -1,17 +1,5 @@
package rpc
import (
"context"
"crypto/x509"
"encoding/pem"
"strings"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/server/certs"
"github.com/bishopfox/sliver/server/db"
"github.com/bishopfox/sliver/server/db/models"
)
/*
Sliver Implant Framework
Copyright (C) 2024 Bishop Fox
@@ -30,6 +18,19 @@ import (
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"context"
"crypto/x509"
"encoding/pem"
"strings"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/server/certs"
"github.com/bishopfox/sliver/server/db"
"github.com/bishopfox/sliver/server/db/models"
)
const (
timeFormat = "2006-01-02 15:04:05 UTC-0700"
)
@@ -47,16 +48,7 @@ func convertDatabaseRecordToProtobuf(record *models.Certificate) *clientpb.Certi
certData.CN = record.CommonName
certData.ID = record.ID.String()
certData.CreationTime = record.CreatedAt.Format(timeFormat)
switch record.CAType {
case certs.MtlsImplantCA:
certData.Type = "MTLS (Implant)"
case certs.MtlsServerCA:
certData.Type = "MTLS (Server)"
case certs.HTTPSCA:
certData.Type = "HTTPS"
default:
certData.Type = record.CAType
}
certData.Type = certificateTypeLabel(record.CAType)
switch record.KeyType {
case certs.ECCKey:
@@ -84,6 +76,67 @@ func convertDatabaseRecordToProtobuf(record *models.Certificate) *clientpb.Certi
return certData
}
func convertAuthorityRecordToProtobuf(record *models.CertificateAuthority) *clientpb.CertificateAuthorityData {
if record == nil {
return nil
}
authData := &clientpb.CertificateAuthorityData{}
authData.ValidityStart = "Unknown (could not parse certificate)"
authData.ValidityExpiry = "Unknown (could not parse certificate)"
authData.CN = record.CommonName
authData.ID = record.ID.String()
authData.CreationTime = record.CreatedAt.Format(timeFormat)
authData.Type = certificateTypeLabel(record.CAType)
authData.KeyAlgorithm = "Unknown"
pemBlock, _ := pem.Decode([]byte(record.CertificatePEM))
if pemBlock == nil {
return authData
}
certificate, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
return authData
}
if authData.CN == "" {
authData.CN = certificate.Subject.CommonName
}
authData.ValidityStart = certificate.NotBefore.Format(timeFormat)
authData.ValidityExpiry = certificate.NotAfter.Format(timeFormat)
switch certificate.PublicKeyAlgorithm {
case x509.ECDSA:
authData.KeyAlgorithm = "ECC"
case x509.RSA:
authData.KeyAlgorithm = "RSA"
case x509.Ed25519:
authData.KeyAlgorithm = "Ed25519"
default:
authData.KeyAlgorithm = strings.ToUpper(certificate.PublicKeyAlgorithm.String())
}
return authData
}
func certificateTypeLabel(caType string) string {
switch caType {
case certs.MtlsImplantCA:
return "MTLS (Implant)"
case certs.MtlsServerCA:
return "MTLS (Server)"
case certs.HTTPSCA:
return "HTTPS"
case certs.OperatorCA:
return "Operator"
default:
return caType
}
}
func (rpc *Server) GetCertificateInfo(ctx context.Context, req *clientpb.CertificatesReq) (*clientpb.CertificateInfo, error) {
certInfo := clientpb.CertificateInfo{}
@@ -100,3 +153,19 @@ func (rpc *Server) GetCertificateInfo(ctx context.Context, req *clientpb.Certifi
return &certInfo, nil
}
func (rpc *Server) GetCertificateAuthorityInfo(ctx context.Context, _ *commonpb.Empty) (*clientpb.CertificateAuthorityInfo, error) {
authInfo := clientpb.CertificateAuthorityInfo{}
caInfoDB, err := db.CertificateAuthorities()
if err != nil {
return nil, rpcError(err)
}
authInfo.Info = make([]*clientpb.CertificateAuthorityData, len(caInfoDB))
for idx, record := range caInfoDB {
authInfo.Info[idx] = convertAuthorityRecordToProtobuf(record)
}
return &authInfo, nil
}
+6 -1
View File
@@ -20,10 +20,13 @@ package rpc
import (
"context"
"fmt"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/server/sgn"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// ShellcodeEncode - Encode a piece shellcode
@@ -35,9 +38,11 @@ func (rpc *Server) ShellcodeEncoder(ctx context.Context, req *clientpb.Shellcode
switch req.Encoder {
case clientpb.ShellcodeEncoder_SHIKATA_GA_NAI:
rpcLog.Infof("[rpc] Shellcode encoder request for: SHIKATA_GA_NAI")
rpcLog.Infof("[rpc] Encoding shellcode (%d bytes) for architecture %s with %d iterations and badchars: %v", len(req.Data), req.Architecture, req.Iterations, req.BadChars)
resp.Data, err = sgn.EncodeShellcode(req.Data, req.Architecture, int(req.Iterations), req.BadChars)
if err != nil {
resp.Response.Err = err.Error()
rpcLog.Errorf("[rpc] Failed to encode shellcode: %v", err)
return nil, status.Error(codes.Internal, fmt.Sprintf("Failed to encode shellcode (%s)", err))
}
default:
resp.Response.Err = "Unknown encoder"
+322 -50
View File
@@ -20,8 +20,11 @@ package rpc
import (
"context"
"fmt"
"io"
"sync"
"sync/atomic"
"time"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/rpcpb"
@@ -32,15 +35,16 @@ import (
var (
// SessionID->Tunnels[TunnelID]->Tunnel->Cache map[uint64]*sliverpb.SocksData{}
toImplantCacheSocks = socksDataCache{mutex: &sync.RWMutex{}, cache: map[uint64]map[uint64]*sliverpb.SocksData{}}
toImplantCacheSocks = socksDataCache{mutex: &sync.RWMutex{}, cache: map[uint64]map[uint64]*sliverpb.SocksData{}, lastActivity: map[uint64]time.Time{}}
// SessionID->Tunnels[TunnelID]->Tunnel->Cache
fromImplantCacheSocks = socksDataCache{mutex: &sync.RWMutex{}, cache: map[uint64]map[uint64]*sliverpb.SocksData{}}
fromImplantCacheSocks = socksDataCache{mutex: &sync.RWMutex{}, cache: map[uint64]map[uint64]*sliverpb.SocksData{}, lastActivity: map[uint64]time.Time{}}
)
type socksDataCache struct {
mutex *sync.RWMutex
cache map[uint64]map[uint64]*sliverpb.SocksData
mutex *sync.RWMutex
cache map[uint64]map[uint64]*sliverpb.SocksData
lastActivity map[uint64]time.Time
}
func (c *socksDataCache) Add(tunnelID uint64, sequence uint64, tunnelData *sliverpb.SocksData) {
@@ -72,6 +76,7 @@ func (c *socksDataCache) DeleteTun(tunnelID uint64) {
defer c.mutex.Unlock()
delete(c.cache, tunnelID)
delete(c.lastActivity, tunnelID)
}
func (c *socksDataCache) DeleteSeq(tunnelID uint64, sequence uint64) {
@@ -85,69 +90,299 @@ func (c *socksDataCache) DeleteSeq(tunnelID uint64, sequence uint64) {
delete(c.cache[tunnelID], sequence)
}
func (c *socksDataCache) recordActivity(tunnelID uint64) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.lastActivity[tunnelID] = time.Now()
}
// Socks - Open an in-band port forward
const (
writeTimeout = 5 * time.Second
batchSize = 100 // Maximum number of sequences to batch
inactivityCheckInterval = 4 * time.Second
inactivityTimeout = 15 * time.Second
ToImplantTickerInterval = 10 * time.Millisecond // data going towards implant is usually smaller request data
ToClientTickerInterval = 5 * time.Millisecond // data coming back from implant is usually larger response data
)
func (s *Server) SocksProxy(stream rpcpb.SliverRPC_SocksProxyServer) error {
errChan := make(chan error, 2)
defer close(errChan)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
connDone := make(chan struct{})
defer close(connDone)
// Track all goroutines spawned for this session
var wg sync.WaitGroup
defer wg.Wait()
// Track all tunnels created for this session
activeTunnels := make(map[uint64]bool)
var tunnelMutex sync.Mutex
// Cleanup all tunnels on SocksProxy closure
defer func() {
tunnelMutex.Lock()
for tunnelID := range activeTunnels {
if tunnel := core.SocksTunnels.Get(tunnelID); tunnel != nil {
rpcLog.Infof("Cleaning up tunnel %d on proxy closure", tunnelID)
close(tunnel.FromImplant)
tunnel.Client = nil
s.CloseSocks(context.Background(), &sliverpb.Socks{TunnelID: tunnelID})
}
}
tunnelMutex.Unlock()
}()
for {
select {
case err := <-errChan:
rpcLog.Errorf("SocksProxy error: %v", err)
return err
default:
}
fromClient, err := stream.Recv()
if err == io.EOF {
break
return nil
}
//fmt.Println("Send Agent 1 ",fromClient.TunnelID,len(fromClient.Data))
if err != nil {
rpcLog.Warnf("Error on stream recv %s", err)
return rpcError(err)
}
tunnelLog.Debugf("Tunnel %d: From client %d byte(s)",
fromClient.TunnelID, len(fromClient.Data))
socks := core.SocksTunnels.Get(fromClient.TunnelID)
if socks == nil {
return nil
tunnelMutex.Lock()
activeTunnels[fromClient.TunnelID] = true // Mark this as an active tunnel
tunnelMutex.Unlock()
tunnel := core.SocksTunnels.Get(fromClient.TunnelID)
if tunnel == nil {
continue
}
if socks.Client == nil {
socks.Client = stream // Bind client to tunnel
// Send Client
go func() {
for tunnelData := range socks.FromImplant {
fromImplantCacheSocks.Add(fromClient.TunnelID, tunnelData.Sequence, tunnelData)
if tunnel.Client == nil {
tunnel.Client = stream
tunnel.FromImplant = make(chan *sliverpb.SocksData, 100) // Buffered channel for 100 messages
for recv, ok := fromImplantCacheSocks.Get(fromClient.TunnelID, socks.FromImplantSequence); ok; recv, ok = fromImplantCacheSocks.Get(fromClient.TunnelID, socks.FromImplantSequence) {
rpcLog.Debugf("[socks] agent to (Server To Client) Data Sequence %d , Data Size %d ,Data %v\n", socks.FromImplantSequence, len(recv.Data), recv.Data)
socks.Client.Send(&sliverpb.SocksData{
CloseConn: recv.CloseConn,
TunnelID: recv.TunnelID,
Data: recv.Data,
})
fromImplantCacheSocks.DeleteSeq(fromClient.TunnelID, socks.FromImplantSequence)
socks.FromImplantSequence++
// Monitor tunnel goroutines for inactivity and cleanup
wg.Add(1)
go func(tunnelID uint64) {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
rpcLog.Errorf("Recovered from panic in monitor: %v", r)
errChan <- fmt.Errorf("monitor goroutine panic: %v", r)
cancel() // Cancel context in case of a panic
}
}()
ticker := time.NewTicker(inactivityCheckInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-connDone:
return
case <-ticker.C:
tunnel := core.SocksTunnels.Get(tunnelID)
if tunnel == nil || tunnel.Client == nil {
return
}
session := core.Sessions.Get(tunnel.SessionID)
// Check both caches for activity
toImplantCacheSocks.mutex.RLock()
fromImplantCacheSocks.mutex.RLock()
toLastActivity := toImplantCacheSocks.lastActivity[tunnelID]
fromLastActivity := fromImplantCacheSocks.lastActivity[tunnelID]
toImplantCacheSocks.mutex.RUnlock()
fromImplantCacheSocks.mutex.RUnlock()
// Clean up goroutine if both directions have hit the idle threshold
if time.Since(toLastActivity) > inactivityTimeout && time.Since(fromLastActivity) > inactivityTimeout {
s.CloseSocks(context.Background(), &sliverpb.Socks{TunnelID: tunnelID})
return
}
// Clean up goroutine if the client has disconnected early
if tunnel.Client == nil || session == nil {
s.CloseSocks(context.Background(), &sliverpb.Socks{TunnelID: tunnelID})
return
}
}
}
}(fromClient.TunnelID)
// Send Client
wg.Add(1)
go func() {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
rpcLog.Errorf("Recovered from panic in client sender: %v", r)
errChan <- fmt.Errorf("client sender panic: %v", r)
cancel() // Cancel context in case of a panic
}
}()
pendingData := make(map[uint64]*sliverpb.SocksData)
ticker := time.NewTicker(ToClientTickerInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-connDone:
return
case tunnelData, ok := <-tunnel.FromImplant:
if !ok {
return
}
// Check if implant is requesting to close the tunnel
if tunnelData.CloseConn {
// Clean up the tunnel
s.CloseSocks(context.Background(), &sliverpb.Socks{TunnelID: fromClient.TunnelID})
return
}
sequence := tunnelData.Sequence
fromImplantCacheSocks.Add(fromClient.TunnelID, sequence, tunnelData)
pendingData[sequence] = tunnelData
fromImplantCacheSocks.recordActivity(fromClient.TunnelID)
case <-ticker.C:
if tunnel.Client == nil {
return
}
if len(pendingData) == 0 {
continue
}
expectedSequence := atomic.LoadUint64(&tunnel.FromImplantSequence)
processed := 0
// Perform Batching
for i := 0; i < batchSize && processed < len(pendingData); i++ {
data, exists := pendingData[expectedSequence]
if !exists {
break // Stop batching if we don't have the next expected sequence
}
func() {
defer func() {
if r := recover(); r != nil {
errChan <- fmt.Errorf("Client sender panic: %v", r)
}
}()
err := stream.Send(&sliverpb.SocksData{
CloseConn: data.CloseConn,
TunnelID: data.TunnelID,
Data: data.Data,
})
if err != nil {
rpcLog.Errorf("Send error: %v", err)
return
}
delete(pendingData, expectedSequence)
fromImplantCacheSocks.DeleteSeq(fromClient.TunnelID, expectedSequence)
atomic.AddUint64(&tunnel.FromImplantSequence, 1)
expectedSequence++
processed++
}()
}
}
}
}()
// Send Agent
wg.Add(1)
go func() {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
rpcLog.Errorf("Recovered from panic in agent sender: %v", r)
errChan <- fmt.Errorf("agent sender panic: %v", r)
cancel() // Cancel context in case of a panic
}
}()
pendingData := make(map[uint64]*sliverpb.SocksData)
ticker := time.NewTicker(ToImplantTickerInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-connDone:
return
case <-ticker.C:
if tunnel.Client == nil {
return
}
sequence := atomic.LoadUint64(&tunnel.ToImplantSequence)
func() {
defer func() {
if r := recover(); r != nil {
rpcLog.Errorf("Recovered from processing panic: %v", r)
}
}()
for {
recv, ok := toImplantCacheSocks.Get(fromClient.TunnelID, sequence)
if !ok {
break
}
session := core.Sessions.Get(fromClient.Request.SessionID)
if session == nil {
rpcLog.Error("Session not found")
break
}
data, err := proto.Marshal(recv)
if err != nil {
rpcLog.Errorf("Failed to marshal data: %v", err)
continue
}
select {
case session.Connection.Send <- &sliverpb.Envelope{
Type: sliverpb.MsgSocksData,
Data: data,
}:
toImplantCacheSocks.DeleteSeq(fromClient.TunnelID, sequence)
atomic.AddUint64(&tunnel.ToImplantSequence, 1)
sequence++
case <-time.After(writeTimeout):
rpcLog.Error("Write timeout to implant")
pendingData[sequence] = recv
break
}
}
}()
}
}
}()
}
// Send Agent
go func() {
toImplantCacheSocks.Add(fromClient.TunnelID, fromClient.Sequence, fromClient)
for recv, ok := toImplantCacheSocks.Get(fromClient.TunnelID, socks.ToImplantSequence); ok; recv, ok = toImplantCacheSocks.Get(fromClient.TunnelID, socks.ToImplantSequence) {
rpcLog.Debugf("[socks] Client to (Server To Agent) Data Sequence %d , Data Size %d \n", socks.ToImplantSequence, len(fromClient.Data))
data, _ := proto.Marshal(recv)
session := core.Sessions.Get(fromClient.Request.SessionID)
session.Connection.Send <- &sliverpb.Envelope{
Type: sliverpb.MsgSocksData,
Data: data,
}
toImplantCacheSocks.DeleteSeq(fromClient.TunnelID, socks.ToImplantSequence)
socks.ToImplantSequence++
}
}()
toImplantCacheSocks.Add(fromClient.TunnelID, fromClient.Sequence, fromClient)
}
return nil
}
// CreateSocks5 - Create requests we close a Socks
@@ -169,11 +404,48 @@ func (s *Server) CreateSocks(ctx context.Context, req *sliverpb.Socks) (*sliverp
// CloseSocks - Client requests we close a Socks
func (s *Server) CloseSocks(ctx context.Context, req *sliverpb.Socks) (*commonpb.Empty, error) {
err := core.SocksTunnels.Close(req.TunnelID)
defer func() {
if r := recover(); r != nil {
rpcLog.Errorf("Recovered from panic in CloseSocks for tunnel %d: %v", req.TunnelID, r)
}
}()
tunnel := core.SocksTunnels.Get(req.TunnelID)
if tunnel != nil {
// Signal close to implant first
if session := core.Sessions.Get(tunnel.SessionID); session != nil {
data, _ := proto.Marshal(&sliverpb.SocksData{
TunnelID: req.TunnelID,
CloseConn: true,
})
session.Connection.Send <- &sliverpb.Envelope{
Type: sliverpb.MsgSocksData,
Data: data,
}
}
time.Sleep(100 * time.Millisecond) // Delay to allow close message to be sent
tunnel.Client = nil // Cleanup the tunnel
if tunnel.FromImplant != nil {
select {
case _, ok := <-tunnel.FromImplant:
if ok {
close(tunnel.FromImplant)
}
default:
close(tunnel.FromImplant)
}
tunnel.FromImplant = nil
}
}
// Clean up caches
toImplantCacheSocks.DeleteTun(req.TunnelID)
fromImplantCacheSocks.DeleteTun(req.TunnelID)
if err != nil {
return nil, rpcError(err)
// Remove from core tunnels last
if err := core.SocksTunnels.Close(req.TunnelID); err != nil {
rpcLog.Errorf("Error closing tunnel %d: %v", req.TunnelID, err)
}
return &commonpb.Empty{}, nil
}
+2
View File
@@ -31,6 +31,7 @@ import (
"github.com/bishopfox/sliver/server/core"
"github.com/bishopfox/sliver/server/db"
"github.com/bishopfox/sliver/server/log"
"github.com/bishopfox/sliver/server/notifications"
"github.com/bishopfox/sliver/server/version"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
@@ -78,6 +79,7 @@ type GenericResponse interface {
// NewServer - Create new server instance
func NewServer() *Server {
core.StartEventAutomation()
notifications.Start()
return &Server{}
}
+8 -5
View File
@@ -56,9 +56,12 @@ type SGNConfig struct {
// EncodeShellcode - Encode a shellcode
func EncodeShellcode(shellcode []byte, arch string, iterations int, badChars []byte) ([]byte, error) {
cfg := SGNConfig{
Architecture: arch,
Iterations: iterations,
BadChars: badChars,
Architecture: arch,
Iterations: iterations,
BadChars: badChars,
PlainDecoder: false,
Safe: true,
MaxObfuscation: 100,
}
return EncodeShellcodeWithConfig(shellcode, cfg)
}
@@ -83,10 +86,10 @@ func EncodeShellcodeWithConfig(shellcode []byte, cfg SGNConfig) ([]byte, error)
needsConstraints := cfg.Asci || len(cfg.BadChars) > 0
seed := sgnpkg.GetRandomByte()
const maxAttempts = 512
const maxAttempts = 64
var lastErr error
for attempt := 0; attempt < maxAttempts; attempt++ {
for attempt := range maxAttempts {
encoder, err := newEncoderWithConfig(arch, cfg)
if err != nil {
sgnLog.Errorf("[sgn] EncodeShellcode setup failed: %s", err)
+2
View File
@@ -0,0 +1,2 @@
7.3.2
# Keep this pinned version in parity with cel-go
+2
View File
@@ -0,0 +1,2 @@
*.pb.go linguist-generated=true
*.pb.go -diff -merge
+2
View File
@@ -0,0 +1,2 @@
bazel-*
MODULE.bazel.lock
+33
View File
@@ -0,0 +1,33 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
package(default_visibility = ["//visibility:public"])
licenses(["notice"]) # Apache 2.0
go_library(
name = "expr",
srcs = [
"checked.pb.go",
"eval.pb.go",
"explain.pb.go",
"syntax.pb.go",
"value.pb.go",
],
importpath = "cel.dev/expr",
visibility = ["//visibility:public"],
deps = [
"@org_golang_google_protobuf//reflect/protoreflect",
"@org_golang_google_protobuf//runtime/protoimpl",
"@org_golang_google_protobuf//types/known/anypb",
"@org_golang_google_protobuf//types/known/durationpb",
"@org_golang_google_protobuf//types/known/emptypb",
"@org_golang_google_protobuf//types/known/structpb",
"@org_golang_google_protobuf//types/known/timestamppb",
],
)
alias(
name = "go_default_library",
actual = ":expr",
visibility = ["//visibility:public"],
)
+25
View File
@@ -0,0 +1,25 @@
# Contributor Code of Conduct
## Version 0.1.1 (adapted from 0.3b-angular)
As contributors and maintainers of the Common Expression Language
(CEL) project, we pledge to respect everyone who contributes by
posting issues, updating documentation, submitting pull requests,
providing feedback in comments, and any other activities.
Communication through any of CEL's channels (GitHub, Gitter, IRC,
mailing lists, Google+, Twitter, etc.) must be constructive and never
resort to personal attacks, trolling, public or private harassment,
insults, or other unprofessional conduct.
We promise to extend courtesy and respect to everyone involved in this
project regardless of gender, gender identity, sexual orientation,
disability, age, race, ethnicity, religion, or level of experience. We
expect anyone contributing to the project to do the same.
If any member of the community violates this code of conduct, the
maintainers of the CEL project may take action, removing issues,
comments, and PRs or blocking accounts as deemed appropriate.
If you are subject to or witness unacceptable behavior, or have any
other concerns, please email us at
[[email protected]](mailto:[email protected]).
+32
View File
@@ -0,0 +1,32 @@
# How to Contribute
We'd love to accept your patches and contributions to this project. There are a
few guidelines you need to follow.
## Contributor License Agreement
Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution,
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.
You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.
## Code reviews
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.
## What to expect from maintainers
Expect maintainers to respond to new issues or pull requests within a week.
For outstanding and ongoing issues and particularly for long-running
pull requests, expect the maintainers to review within a week of a
contributor asking for a new review. There is no commitment to resolution --
merging or closing a pull request, or fixing or closing an issue -- because some
issues will require more discussion than others.
+43
View File
@@ -0,0 +1,43 @@
# Project Governance
This document defines the governance process for the CEL language. CEL is
Google-developed, but openly governed. Major contributors to the CEL
specification and its corresponding implementations constitute the CEL
Language Council. New members may be added by a unanimous vote of the
Council.
The MAINTAINERS.md file lists the members of the CEL Language Council, and
unofficially indicates the "areas of expertise" of each member with respect
to the publicly available CEL repos.
## Code Changes
Code changes must follow the standard pull request (PR) model documented in the
CONTRIBUTING.md for each CEL repo. All fixes and features must be reviewed by a
maintainer. The maintainer reserves the right to request that any feature
request (FR) or PR be reviewed by the language council.
## Syntax and Semantic Changes
Syntactic and semantic changes must be reviewed by the CEL Language Council.
Maintainers may also request language council review at their discretion.
The review process is as follows:
- Create a Feature Request in the CEL-Spec repo. The feature description will
serve as an abstract for the detailed design document.
- Co-develop a design document with the Language Council.
- Once the proposer gives the design document approval, the document will be
linked to the FR in the CEL-Spec repo and opened for comments to members of
the cel-lang-discuss@googlegroups.com.
- The Language Council will review the design doc at the next council meeting
(once every three weeks) and the council decision included in the document.
If the proposal is approved, the spec will be updated by a maintainer (if
applicable) and a rationale will be included in the CEL-Spec wiki to ensure
future developers may follow CEL's growth and direction over time.
Approved proposals may be implemented by the proposer or by the maintainers as
the parties see fit. At the discretion of the maintainer, changes from the
approved design are permitted during implementation if they improve the user
experience and clarity of the feature.
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+13
View File
@@ -0,0 +1,13 @@
# CEL Language Council
| Name | Company | Area of Expertise |
|-----------------|--------------|-------------------|
| Alfred Fuller | Facebook | cel-cpp, cel-spec |
| Jim Larson | Google | cel-go, cel-spec |
| Matthais Blume | Google | cel-spec |
| Tristan Swadell | Google | cel-go, cel-spec |
## Emeritus
* Sanjay Ghemawat (Google)
* Wolfgang Grieskamp (Facebook)
+56
View File
@@ -0,0 +1,56 @@
module(
name = "cel-spec",
)
bazel_dep(
name = "bazel_skylib",
version = "1.7.1",
)
bazel_dep(
name = "gazelle",
version = "0.39.1",
repo_name = "bazel_gazelle",
)
bazel_dep(
name = "protobuf",
version = "27.1",
repo_name = "com_google_protobuf",
)
bazel_dep(
name = "rules_cc",
version = "0.0.17",
)
bazel_dep(
name = "rules_go",
version = "0.53.0",
repo_name = "io_bazel_rules_go",
)
bazel_dep(
name = "rules_java",
version = "7.6.5",
)
bazel_dep(
name = "rules_proto",
version = "7.0.2",
)
bazel_dep(
name = "rules_python",
version = "0.35.0",
)
### PYTHON ###
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(
ignore_root_user_error = True,
python_version = "3.11",
)
go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk")
go_sdk.download(version = "1.23.0")
go_deps = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(
go_deps,
"org_golang_google_protobuf",
)
+71
View File
@@ -0,0 +1,71 @@
# Common Expression Language
The Common Expression Language (CEL) implements common semantics for expression
evaluation, enabling different applications to more easily interoperate.
Key Applications
* Security policy: organizations have complex infrastructure and need common
tooling to reason about the system as a whole
* Protocols: expressions are a useful data type and require interoperability
across programming languages and platforms.
Guiding philosophy:
1. Keep it small & fast.
* CEL evaluates in linear time, is mutation free, and not Turing-complete.
This limitation is a feature of the language design, which allows the
implementation to evaluate orders of magnitude faster than equivalently
sandboxed JavaScript.
2. Make it extensible.
* CEL is designed to be embedded in applications, and allows for
extensibility via its context which allows for functions and data to be
provided by the software that embeds it.
3. Developer-friendly.
* The language is approachable to developers. The initial spec was based
on the experience of developing Firebase Rules and usability testing
many prior iterations.
* The library itself and accompanying toolings should be easy to adopt by
teams that seek to integrate CEL into their platforms.
The required components of a system that supports CEL are:
* The textual representation of an expression as written by a developer. It is
of similar syntax to expressions in C/C++/Java/JavaScript
* A representation of the program's abstract syntax tree (AST).
* A compiler library that converts the textual representation to the binary
representation. This can be done ahead of time (in the control plane) or
just before evaluation (in the data plane).
* A context containing one or more typed variables, often protobuf messages.
Most use-cases will use `attribute_context.proto`
* An evaluator library that takes the binary format in the context and
produces a result, usually a Boolean.
For use cases which require persistence or cross-process communcation, it is
highly recommended to serialize the type-checked expression as a protocol
buffer. The CEL team will maintains canonical protocol buffers for ASTs and
will keep these versions identical and wire-compatible in perpetuity:
* [CEL canonical](https://github.com/google/cel-spec/tree/master/proto/cel/expr)
* [CEL v1alpha1](https://github.com/googleapis/googleapis/tree/master/google/api/expr/v1alpha1)
Example of boolean conditions and object construction:
``` c
// Condition
account.balance >= transaction.withdrawal
|| (account.overdraftProtection
&& account.overdraftLimit >= transaction.withdrawal - account.balance)
// Object construction
common.GeoPoint{ latitude: 10.0, longitude: -5.5 }
```
For more detail, see:
* [Introduction](doc/intro.md)
* [Language Definition](doc/langdef.md)
Released under the [Apache License](LICENSE).
+145
View File
@@ -0,0 +1,145 @@
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "io_bazel_rules_go",
sha256 = "099a9fb96a376ccbbb7d291ed4ecbdfd42f6bc822ab77ae6f1b5cb9e914e94fa",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.35.0/rules_go-v0.35.0.zip",
"https://github.com/bazelbuild/rules_go/releases/download/v0.35.0/rules_go-v0.35.0.zip",
],
)
http_archive(
name = "bazel_gazelle",
sha256 = "ecba0f04f96b4960a5b250c8e8eeec42281035970aa8852dda73098274d14a1d",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.29.0/bazel-gazelle-v0.29.0.tar.gz",
"https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.29.0/bazel-gazelle-v0.29.0.tar.gz",
],
)
http_archive(
name = "rules_proto",
sha256 = "e017528fd1c91c5a33f15493e3a398181a9e821a804eb7ff5acdd1d2d6c2b18d",
strip_prefix = "rules_proto-4.0.0-3.20.0",
urls = [
"https://github.com/bazelbuild/rules_proto/archive/refs/tags/4.0.0-3.20.0.tar.gz",
],
)
# googleapis as of 09/16/2024
http_archive(
name = "com_google_googleapis",
strip_prefix = "googleapis-4082d5e51e8481f6ccc384cacd896f4e78f19dee",
sha256 = "57319889d47578b3c89bf1b3f34888d796a8913d63b32d750a4cd12ed303c4e8",
urls = [
"https://github.com/googleapis/googleapis/archive/4082d5e51e8481f6ccc384cacd896f4e78f19dee.tar.gz",
],
)
# protobuf
http_archive(
name = "com_google_protobuf",
sha256 = "8242327e5df8c80ba49e4165250b8f79a76bd11765facefaaecfca7747dc8da2",
strip_prefix = "protobuf-3.21.5",
urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.21.5.zip"],
)
# googletest
http_archive(
name = "com_google_googletest",
urls = ["https://github.com/google/googletest/archive/master.zip"],
strip_prefix = "googletest-master",
)
# gflags
http_archive(
name = "com_github_gflags_gflags",
sha256 = "6e16c8bc91b1310a44f3965e616383dbda48f83e8c1eaa2370a215057b00cabe",
strip_prefix = "gflags-77592648e3f3be87d6c7123eb81cbad75f9aef5a",
urls = [
"https://mirror.bazel.build/github.com/gflags/gflags/archive/77592648e3f3be87d6c7123eb81cbad75f9aef5a.tar.gz",
"https://github.com/gflags/gflags/archive/77592648e3f3be87d6c7123eb81cbad75f9aef5a.tar.gz",
],
)
# glog
http_archive(
name = "com_google_glog",
sha256 = "1ee310e5d0a19b9d584a855000434bb724aa744745d5b8ab1855c85bff8a8e21",
strip_prefix = "glog-028d37889a1e80e8a07da1b8945ac706259e5fd8",
urls = [
"https://mirror.bazel.build/github.com/google/glog/archive/028d37889a1e80e8a07da1b8945ac706259e5fd8.tar.gz",
"https://github.com/google/glog/archive/028d37889a1e80e8a07da1b8945ac706259e5fd8.tar.gz",
],
)
# absl
http_archive(
name = "com_google_absl",
strip_prefix = "abseil-cpp-master",
urls = ["https://github.com/abseil/abseil-cpp/archive/master.zip"],
)
load("@io_bazel_rules_go//go:deps.bzl", "go_rules_dependencies", "go_register_toolchains")
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
load("@com_google_googleapis//:repository_rules.bzl", "switched_rules_by_language")
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
switched_rules_by_language(
name = "com_google_googleapis_imports",
cc = True,
)
# Do *not* call *_dependencies(), etc, yet. See comment at the end.
# Generated Google APIs protos for Golang
# Generated Google APIs protos for Golang 08/26/2024
go_repository(
name = "org_golang_google_genproto_googleapis_api",
build_file_proto_mode = "disable_global",
importpath = "google.golang.org/genproto/googleapis/api",
sum = "h1:YcyjlL1PRr2Q17/I0dPk2JmYS5CDXfcdb2Z3YRioEbw=",
version = "v0.0.0-20240826202546-f6391c0de4c7",
)
# Generated Google APIs protos for Golang 08/26/2024
go_repository(
name = "org_golang_google_genproto_googleapis_rpc",
build_file_proto_mode = "disable_global",
importpath = "google.golang.org/genproto/googleapis/rpc",
sum = "h1:2035KHhUv+EpyB+hWgJnaWKJOdX1E95w2S8Rr4uWKTs=",
version = "v0.0.0-20240826202546-f6391c0de4c7",
)
# gRPC deps
go_repository(
name = "org_golang_google_grpc",
build_file_proto_mode = "disable_global",
importpath = "google.golang.org/grpc",
tag = "v1.49.0",
)
go_repository(
name = "org_golang_x_net",
importpath = "golang.org/x/net",
sum = "h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=",
version = "v0.0.0-20190311183353-d8887717615a",
)
go_repository(
name = "org_golang_x_text",
importpath = "golang.org/x/text",
sum = "h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=",
version = "v0.3.2",
)
# Run the dependencies at the end. These will silently try to import some
# of the above repositories but at different versions, so ours must come first.
go_rules_dependencies()
go_register_toolchains(version = "1.19.1")
gazelle_dependencies()
rules_proto_dependencies()
rules_proto_toolchains()
protobuf_deps()
View File
Whitespace-only changes.
+1231
View File
File diff suppressed because it is too large. Load diff
+9
View File
@@ -0,0 +1,9 @@
steps:
- name: 'gcr.io/cloud-builders/bazel:7.3.2'
entrypoint: bazel
args: ['build', '...']
id: bazel-build
waitFor: ['-']
timeout: 15m
options:
machineType: 'N1_HIGHCPU_32'
+468
View File
@@ -0,0 +1,468 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.10
// protoc v5.27.1
// source: cel/expr/eval.proto
package expr
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
anypb "google.golang.org/protobuf/types/known/anypb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type EvalState struct {
state protoimpl.MessageState `protogen:"open.v1"`
Values []*ExprValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
Results []*EvalState_Result `protobuf:"bytes,3,rep,name=results,proto3" json:"results,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EvalState) Reset() {
*x = EvalState{}
mi := &file_cel_expr_eval_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EvalState) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EvalState) ProtoMessage() {}
func (x *EvalState) ProtoReflect() protoreflect.Message {
mi := &file_cel_expr_eval_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EvalState.ProtoReflect.Descriptor instead.
func (*EvalState) Descriptor() ([]byte, []int) {
return file_cel_expr_eval_proto_rawDescGZIP(), []int{0}
}
func (x *EvalState) GetValues() []*ExprValue {
if x != nil {
return x.Values
}
return nil
}
func (x *EvalState) GetResults() []*EvalState_Result {
if x != nil {
return x.Results
}
return nil
}
type ExprValue struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Types that are valid to be assigned to Kind:
//
// *ExprValue_Value
// *ExprValue_Error
// *ExprValue_Unknown
Kind isExprValue_Kind `protobuf_oneof:"kind"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ExprValue) Reset() {
*x = ExprValue{}
mi := &file_cel_expr_eval_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ExprValue) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ExprValue) ProtoMessage() {}
func (x *ExprValue) ProtoReflect() protoreflect.Message {
mi := &file_cel_expr_eval_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ExprValue.ProtoReflect.Descriptor instead.
func (*ExprValue) Descriptor() ([]byte, []int) {
return file_cel_expr_eval_proto_rawDescGZIP(), []int{1}
}
func (x *ExprValue) GetKind() isExprValue_Kind {
if x != nil {
return x.Kind
}
return nil
}
func (x *ExprValue) GetValue() *Value {
if x != nil {
if x, ok := x.Kind.(*ExprValue_Value); ok {
return x.Value
}
}
return nil
}
func (x *ExprValue) GetError() *ErrorSet {
if x != nil {
if x, ok := x.Kind.(*ExprValue_Error); ok {
return x.Error
}
}
return nil
}
func (x *ExprValue) GetUnknown() *UnknownSet {
if x != nil {
if x, ok := x.Kind.(*ExprValue_Unknown); ok {
return x.Unknown
}
}
return nil
}
type isExprValue_Kind interface {
isExprValue_Kind()
}
type ExprValue_Value struct {
Value *Value `protobuf:"bytes,1,opt,name=value,proto3,oneof"`
}
type ExprValue_Error struct {
Error *ErrorSet `protobuf:"bytes,2,opt,name=error,proto3,oneof"`
}
type ExprValue_Unknown struct {
Unknown *UnknownSet `protobuf:"bytes,3,opt,name=unknown,proto3,oneof"`
}
func (*ExprValue_Value) isExprValue_Kind() {}
func (*ExprValue_Error) isExprValue_Kind() {}
func (*ExprValue_Unknown) isExprValue_Kind() {}
type ErrorSet struct {
state protoimpl.MessageState `protogen:"open.v1"`
Errors []*Status `protobuf:"bytes,1,rep,name=errors,proto3" json:"errors,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ErrorSet) Reset() {
*x = ErrorSet{}
mi := &file_cel_expr_eval_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ErrorSet) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ErrorSet) ProtoMessage() {}
func (x *ErrorSet) ProtoReflect() protoreflect.Message {
mi := &file_cel_expr_eval_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ErrorSet.ProtoReflect.Descriptor instead.
func (*ErrorSet) Descriptor() ([]byte, []int) {
return file_cel_expr_eval_proto_rawDescGZIP(), []int{2}
}
func (x *ErrorSet) GetErrors() []*Status {
if x != nil {
return x.Errors
}
return nil
}
type Status struct {
state protoimpl.MessageState `protogen:"open.v1"`
Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
Details []*anypb.Any `protobuf:"bytes,3,rep,name=details,proto3" json:"details,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Status) Reset() {
*x = Status{}
mi := &file_cel_expr_eval_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Status) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Status) ProtoMessage() {}
func (x *Status) ProtoReflect() protoreflect.Message {
mi := &file_cel_expr_eval_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Status.ProtoReflect.Descriptor instead.
func (*Status) Descriptor() ([]byte, []int) {
return file_cel_expr_eval_proto_rawDescGZIP(), []int{3}
}
func (x *Status) GetCode() int32 {
if x != nil {
return x.Code
}
return 0
}
func (x *Status) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
func (x *Status) GetDetails() []*anypb.Any {
if x != nil {
return x.Details
}
return nil
}
type UnknownSet struct {
state protoimpl.MessageState `protogen:"open.v1"`
Exprs []int64 `protobuf:"varint,1,rep,packed,name=exprs,proto3" json:"exprs,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UnknownSet) Reset() {
*x = UnknownSet{}
mi := &file_cel_expr_eval_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UnknownSet) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UnknownSet) ProtoMessage() {}
func (x *UnknownSet) ProtoReflect() protoreflect.Message {
mi := &file_cel_expr_eval_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UnknownSet.ProtoReflect.Descriptor instead.
func (*UnknownSet) Descriptor() ([]byte, []int) {
return file_cel_expr_eval_proto_rawDescGZIP(), []int{4}
}
func (x *UnknownSet) GetExprs() []int64 {
if x != nil {
return x.Exprs
}
return nil
}
type EvalState_Result struct {
state protoimpl.MessageState `protogen:"open.v1"`
Expr int64 `protobuf:"varint,1,opt,name=expr,proto3" json:"expr,omitempty"`
Value int64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EvalState_Result) Reset() {
*x = EvalState_Result{}
mi := &file_cel_expr_eval_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EvalState_Result) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EvalState_Result) ProtoMessage() {}
func (x *EvalState_Result) ProtoReflect() protoreflect.Message {
mi := &file_cel_expr_eval_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EvalState_Result.ProtoReflect.Descriptor instead.
func (*EvalState_Result) Descriptor() ([]byte, []int) {
return file_cel_expr_eval_proto_rawDescGZIP(), []int{0, 0}
}
func (x *EvalState_Result) GetExpr() int64 {
if x != nil {
return x.Expr
}
return 0
}
func (x *EvalState_Result) GetValue() int64 {
if x != nil {
return x.Value
}
return 0
}
var File_cel_expr_eval_proto protoreflect.FileDescriptor
const file_cel_expr_eval_proto_rawDesc = "" +
"\n" +
"\x13cel/expr/eval.proto\x12\bcel.expr\x1a\x19google/protobuf/any.proto\x1a\x14cel/expr/value.proto\"\xa2\x01\n" +
"\tEvalState\x12+\n" +
"\x06values\x18\x01 \x03(\v2\x13.cel.expr.ExprValueR\x06values\x124\n" +
"\aresults\x18\x03 \x03(\v2\x1a.cel.expr.EvalState.ResultR\aresults\x1a2\n" +
"\x06Result\x12\x12\n" +
"\x04expr\x18\x01 \x01(\x03R\x04expr\x12\x14\n" +
"\x05value\x18\x02 \x01(\x03R\x05value\"\x9a\x01\n" +
"\tExprValue\x12'\n" +
"\x05value\x18\x01 \x01(\v2\x0f.cel.expr.ValueH\x00R\x05value\x12*\n" +
"\x05error\x18\x02 \x01(\v2\x12.cel.expr.ErrorSetH\x00R\x05error\x120\n" +
"\aunknown\x18\x03 \x01(\v2\x14.cel.expr.UnknownSetH\x00R\aunknownB\x06\n" +
"\x04kind\"4\n" +
"\bErrorSet\x12(\n" +
"\x06errors\x18\x01 \x03(\v2\x10.cel.expr.StatusR\x06errors\"f\n" +
"\x06Status\x12\x12\n" +
"\x04code\x18\x01 \x01(\x05R\x04code\x12\x18\n" +
"\amessage\x18\x02 \x01(\tR\amessage\x12.\n" +
"\adetails\x18\x03 \x03(\v2\x14.google.protobuf.AnyR\adetails\"\"\n" +
"\n" +
"UnknownSet\x12\x14\n" +
"\x05exprs\x18\x01 \x03(\x03R\x05exprsB,\n" +
"\fdev.cel.exprB\tEvalProtoP\x01Z\fcel.dev/expr\xf8\x01\x01b\x06proto3"
var (
file_cel_expr_eval_proto_rawDescOnce sync.Once
file_cel_expr_eval_proto_rawDescData []byte
)
func file_cel_expr_eval_proto_rawDescGZIP() []byte {
file_cel_expr_eval_proto_rawDescOnce.Do(func() {
file_cel_expr_eval_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_cel_expr_eval_proto_rawDesc), len(file_cel_expr_eval_proto_rawDesc)))
})
return file_cel_expr_eval_proto_rawDescData
}
var file_cel_expr_eval_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_cel_expr_eval_proto_goTypes = []any{
(*EvalState)(nil), // 0: cel.expr.EvalState
(*ExprValue)(nil), // 1: cel.expr.ExprValue
(*ErrorSet)(nil), // 2: cel.expr.ErrorSet
(*Status)(nil), // 3: cel.expr.Status
(*UnknownSet)(nil), // 4: cel.expr.UnknownSet
(*EvalState_Result)(nil), // 5: cel.expr.EvalState.Result
(*Value)(nil), // 6: cel.expr.Value
(*anypb.Any)(nil), // 7: google.protobuf.Any
}
var file_cel_expr_eval_proto_depIdxs = []int32{
1, // 0: cel.expr.EvalState.values:type_name -> cel.expr.ExprValue
5, // 1: cel.expr.EvalState.results:type_name -> cel.expr.EvalState.Result
6, // 2: cel.expr.ExprValue.value:type_name -> cel.expr.Value
2, // 3: cel.expr.ExprValue.error:type_name -> cel.expr.ErrorSet
4, // 4: cel.expr.ExprValue.unknown:type_name -> cel.expr.UnknownSet
3, // 5: cel.expr.ErrorSet.errors:type_name -> cel.expr.Status
7, // 6: cel.expr.Status.details:type_name -> google.protobuf.Any
7, // [7:7] is the sub-list for method output_type
7, // [7:7] is the sub-list for method input_type
7, // [7:7] is the sub-list for extension type_name
7, // [7:7] is the sub-list for extension extendee
0, // [0:7] is the sub-list for field type_name
}
func init() { file_cel_expr_eval_proto_init() }
func file_cel_expr_eval_proto_init() {
if File_cel_expr_eval_proto != nil {
return
}
file_cel_expr_value_proto_init()
file_cel_expr_eval_proto_msgTypes[1].OneofWrappers = []any{
(*ExprValue_Value)(nil),
(*ExprValue_Error)(nil),
(*ExprValue_Unknown)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_cel_expr_eval_proto_rawDesc), len(file_cel_expr_eval_proto_rawDesc)),
NumEnums: 0,
NumMessages: 6,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_cel_expr_eval_proto_goTypes,
DependencyIndexes: file_cel_expr_eval_proto_depIdxs,
MessageInfos: file_cel_expr_eval_proto_msgTypes,
}.Build()
File_cel_expr_eval_proto = out.File
file_cel_expr_eval_proto_goTypes = nil
file_cel_expr_eval_proto_depIdxs = nil
}
Loaded 100 of 4359 files, more files were not shown because too many files have changed in this diff. Show more