Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions acl/acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ const (
// secret values.
ActionInfo = Action("info")

// ActionSetInfo ("set-info" in the API) denotes permission to write the
// metadata for a secret, including the human-readable description, but not
// the secret values.
ActionSetInfo = Action("set-info")

// ActionPut ("put" in the API) denotes permission to put a new value of a
// secret.
ActionPut = Action("put")
Expand Down
10 changes: 10 additions & 0 deletions acl/acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ func TestACL(t *testing.T) {
Action: []acl.Action{acl.ActionDelete},
Secret: []acl.Secret{"dev/*"},
},
acl.Rule{
Action: []acl.Action{acl.ActionGet, acl.ActionSetInfo},
Secret: []acl.Secret{"special/*/magic"},
},
}

type testCase struct {
Expand Down Expand Up @@ -73,6 +77,12 @@ func TestACL(t *testing.T) {
deny("delete", "control/bar"),
deny("delete", "something/else"),
deny("delete", "dev"),

allow("get", "special/foo/magic"),
deny("get", "special/foo/more-magic"),
allow("set-info", "special/bar/magic"),
deny("set-info", "special/bar/more-magic"),
deny("set-info", "some/other/nonsense"),
}

for _, test := range tests {
Expand Down
9 changes: 9 additions & 0 deletions client/setec/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,15 @@ func (c Client) Info(ctx context.Context, name string) (*api.SecretInfo, error)
})
}

// SetInfo updates metadata for the given secret name.
func (c Client) SetInfo(ctx context.Context, name string, update api.SecretInfoUpdate) error {
_, err := do[struct{}](ctx, c, "/api/set-info", api.SetInfoRequest{
Name: name,
SecretInfoUpdate: update,
})
return err
}

// Put creates a secret called name, with the given value. If a secret called
// name already exist, the value is saved as a new inactive version.
//
Expand Down
34 changes: 32 additions & 2 deletions cmd/setec/setec.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"golang.org/x/term"
"tailscale.com/tsnet"
"tailscale.com/tsweb"
"tailscale.com/types/opt"
)

func main() {
Expand Down Expand Up @@ -97,6 +98,13 @@ Most of the settings can be set via environment variables as well as flags.
Help: "Get metadata for the specified secret.",
Run: command.Adapt(runInfo),
},
{
Name: "set-info",
Usage: "<secret-name>",
Help: `Set metadata for the specified secret.`,
SetFlags: command.Flags(flax.MustBind, &setInfoArgs),
Run: command.Adapt(runSetInfo),
},
{
Name: "get",
Usage: "<secret-name>",
Expand Down Expand Up @@ -327,7 +335,7 @@ func runList(env *command.Env) error {
}

tw := newTabWriter(os.Stdout)
io.WriteString(tw, "NAME\tACTIVE\tVERSIONS\tLAST ACCESSED\n")
io.WriteString(tw, "NAME\tACTIVE\tVERSIONS\tLAST ACCESSED\tDESCRIPTION\n")
for _, s := range secrets {
vers := make([]string, 0, len(s.Versions))
for _, v := range s.Versions {
Expand All @@ -337,7 +345,8 @@ func runList(env *command.Env) error {
if !s.LastAccess.IsZero() {
lastAccess = s.LastAccess.Format(time.RFC3339)
}
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", s.Name, s.ActiveVersion, strings.Join(vers, ","), lastAccess)
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\n",
s.Name, s.ActiveVersion, strings.Join(vers, ","), lastAccess, s.Description)
}
return tw.Flush()
}
Expand All @@ -358,6 +367,9 @@ func runInfo(env *command.Env, name string) error {
}
tw := newTabWriter(os.Stdout)
fmt.Fprintf(tw, "Name:\t%s\n", info.Name)
if d := info.Description; d != "" {
fmt.Fprintf(tw, "Description:\t%s\n", d)
}
fmt.Fprintf(tw, "Active version:\t%s\n", info.ActiveVersion)
fmt.Fprintf(tw, "Versions:\t%s\n", strings.Join(vers, ", "))
if !info.LastAccess.IsZero() {
Expand All @@ -366,6 +378,24 @@ func runInfo(env *command.Env, name string) error {
return tw.Flush()
}

var setInfoArgs struct {
Description string `flag:"description,Set the human-readable description of the secret"`
}

func runSetInfo(env *command.Env, name string) error {
c, err := newClient()
if err != nil {
return err
}

// If more metadata fields are added, this list will need to be extended.
var update api.SecretInfoUpdate
if env.IsFlagSet("description") {
update.Description = opt.ValueOf(setInfoArgs.Description)
}
return c.SetInfo(env.Context(), name, update)
}

var getArgs struct {
IfChanged bool `flag:"if-changed,Get active version if changed from --version"`
Version uint64 `flag:"version,Secret version to retrieve (default: the active version)"`
Expand Down
18 changes: 18 additions & 0 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ var (
// ErrInvalidVersion indicates that an attempt was made to create a
// version of a secret using an invalid version number (<=0).
ErrInvalidVersion = errors.New("invalid version")
// ErrInvalidParams indicates that one or more request parameters are
// invalid (in an otherwise-authorized request).
ErrInvalidParams = errors.New("invalid parameters")
)

// Config carries the parameters required to construct a [DB].
Expand Down Expand Up @@ -388,6 +391,21 @@ func (db *DB) deleteConfigLocked(name string) error {
return fmt.Errorf("unknown config value %q", name)
}

// SetInfo updates the metadata of the specified secret with the specified
// values. It reports an error if the secret does not exist, or if the
func (db *DB) SetInfo(caller Caller, name string, update api.SecretInfoUpdate) error {
db.mu.Lock()
defer db.mu.Unlock()

if err := db.checkAndLog(caller, acl.ActionSetInfo, name, 0); err != nil {
return err
}
if strings.HasPrefix(name, configPrefix) {
return fmt.Errorf("cannot set info for config %q", name)
}
return db.kv.setInfo(name, update)
}

// AccessIndex is an index mapping secret names to last-access records.
type AccessIndex map[string]LastAccess

Expand Down
47 changes: 47 additions & 0 deletions db/kv.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"maps"
"os"
"slices"
"unicode/utf8"

"github.com/tailscale/setec/types/api"
"github.com/tink-crypto/tink-go/v2/aead"
Expand Down Expand Up @@ -91,15 +92,22 @@ type secret struct {
// We rely on api.SecretVersion being a type encoding/json will translate to
// a JSON string (currently an integer).
Versions map[api.SecretVersion]byteString

// ActiveVersion is the secret version that gets returned to
// clients who don't ask for a specific version of the secret.
ActiveVersion api.SecretVersion

// LatestVersion is the highest version that has already been used
// by a previous Put or CreateVersion.
LatestVersion api.SecretVersion

// DeletedVersions tracks versions that were previously set but
// have since been deleted. These are not permitted to be set again.
DeletedVersions map[api.SecretVersion]bool

// Description is an optional human-readable text describing the role or
// purpose of the secret. A valid Description must be UTF-8 encoded.
Description string
}

// byteString is an alias for a string, but encodes to JSON as the conventional
Expand Down Expand Up @@ -279,6 +287,7 @@ func (kv *kv) info(name string) (*api.SecretInfo, error) {
info := &api.SecretInfo{
Name: name,
ActiveVersion: secret.ActiveVersion,
Description: secret.Description,
}
for v := range secret.Versions {
info.Versions = append(info.Versions, v)
Expand All @@ -287,6 +296,44 @@ func (kv *kv) info(name string) (*api.SecretInfo, error) {
return info, nil
}

// setInfo applies an update to the metadata of a secret.
func (kv *kv) setInfo(name string, update api.SecretInfoUpdate) error {
secret := kv.secrets[name]
if secret == nil {
return ErrNotFound
}

// Make a shallow copy of the secret so we can revert if save fails.
// An update does not modify the maps, so it's safe to share them.
backup := *secret

// Apply any set fields of the update.
// We require that at least one update is set. When adding new metadata
// fields, add additional conditional blocks below.

var hasUpdate bool
if desc, ok := update.Description.GetOk(); ok {
if !utf8.ValidString(desc) {
return fmt.Errorf("%w: description is not utf-8", ErrInvalidParams)
} else if len(desc) > api.MaxDescriptionBytes {
return fmt.Errorf("%w: description too long (%d bytes > %d)",
ErrInvalidParams, len(desc), api.MaxDescriptionBytes)
}
secret.Description = desc
hasUpdate = true
}
// .. add additional fields here

if !hasUpdate {
return fmt.Errorf("%w: no updates specified", ErrInvalidParams)
}
if err := kv.save(); err != nil {
*secret = backup // restore
return err
}
return nil
}

// get returns a secret's active value.
func (kv *kv) get(name string) (*api.SecretValue, error) {
secret := kv.secrets[name]
Expand Down
14 changes: 14 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,20 @@ The service defines named _actions_ that are subject to access control:
{"Name":"example","Versions":[1,2,3],"ActiveVersion":2}
```

- `/api/set-info`: Set metadata for a single secret.

**Requires:** `set-info` permission for the specified secret.

**Request:** `api.SetInfoRequest`

**Example request:**
``json
{"Name":"example","Description":"a demonstration secret, not used in production"}`
```

**Constraints:** The value of the *Description* field must be valid UTF-8 and may
not exceed 1000 bytes in length.

- `/api/put`: Add a new value for a secret.

**Requires:** `put` permission for the specified name.
Expand Down
19 changes: 9 additions & 10 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@ require (
github.com/aws/aws-sdk-go-v2/credentials v1.19.5
github.com/aws/aws-sdk-go-v2/service/s3 v1.93.2
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5
github.com/creachadair/command v0.2.0
github.com/creachadair/command v0.2.4
github.com/creachadair/flax v0.0.5
github.com/creachadair/mds v0.25.15
github.com/creachadair/mds v0.27.1
github.com/creachadair/msync v0.8.1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/google/go-cmp v0.7.0
github.com/tink-crypto/tink-go-awskms/v2 v2.1.0
github.com/tink-crypto/tink-go/v2 v2.6.0
golang.org/x/term v0.38.0
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0
honnef.co/go/tools v0.7.0
tailscale.com v1.92.1
)

Expand Down Expand Up @@ -70,17 +69,17 @@ require (
github.com/x448/float16 v0.8.4 // indirect
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect
golang.org/x/mod v0.30.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.39.0 // indirect
golang.org/x/tools v0.40.1-0.20260108161641-ca281cf95054 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/protobuf v1.36.8 // indirect
Expand Down
36 changes: 18 additions & 18 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NA
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/creachadair/command v0.2.0 h1:qTA9cMMhZePAxFoNdnk6F6nn94s1qPndIg9hJbqI9cA=
github.com/creachadair/command v0.2.0/go.mod h1:j+Ar+uYnFsHpkMeV9kGj6lJ45y9u2xqtg8FYy6cm+0o=
github.com/creachadair/command v0.2.4 h1:dR4ZbdaSIortWQ/ZvGrNlohmtNECJaFyTIMuqlRBSV4=
github.com/creachadair/command v0.2.4/go.mod h1:oZUQWtYwThS+2p91b5OcGhdJuYpSIe5JhExYgQecxU0=
github.com/creachadair/flax v0.0.5 h1:zt+CRuXQASxwQ68e9GHAOnEgAU29nF0zYMHOCrL5wzE=
github.com/creachadair/flax v0.0.5/go.mod h1:F1PML0JZLXSNDMNiRGK2yjm5f+L9QCHchyHBldFymj8=
github.com/creachadair/mds v0.25.15 h1:i8CUqtfgbCqbvZ++L7lm8No3cOeic9YKF4vHEvEoj+Y=
github.com/creachadair/mds v0.25.15/go.mod h1:XtMfRW15sjd1iOi1Z1k+dq0pRsR5xPbulpoTrpyhk8w=
github.com/creachadair/mds v0.27.1 h1:GlO1tPbrsaoafkF6mz7dFutkGXAtIfQLI450u0ypqwA=
github.com/creachadair/mds v0.27.1/go.mod h1:dMBTCSy3iS3dwh4Rb1zxeZz2d7K8+N24GCTsayWtQRI=
github.com/creachadair/msync v0.8.1 h1:QRd8si3qZ2Q4TaDL7tS/MG/lFE3YND7U7J9fy42eAFM=
github.com/creachadair/msync v0.8.1/go.mod h1:dt0bscS09J8Ie3AdccK9JpCb7LfStaDGlAmDLukOlY4=
github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc=
Expand Down Expand Up @@ -209,35 +209,35 @@ go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4
go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
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-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
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=
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
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.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
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/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
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-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
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.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.40.1-0.20260108161641-ca281cf95054 h1:CHVDrNHx9ZoOrNN9kKWYIbT5Rj+WF2rlwPkhbQQ5V4U=
golang.org/x/tools v0.40.1-0.20260108161641-ca281cf95054/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
Expand All @@ -252,8 +252,8 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k=
gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM=
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0 h1:5SXjd4ET5dYijLaf0O3aOenC0Z4ZafIWSpjUzsQaNho=
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0/go.mod h1:EPDDhEZqVHhWuPI5zPAsjU0U7v9xNIWjoOVyZ5ZcniQ=
honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU=
honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
Expand Down
Loading
Loading