fix(auth): tighten bridge claim protocol

This commit is contained in:
2026-08-16 22:44:44 +02:00
parent 7d9ca13f1d
commit 8477a69a29
7 changed files with 333 additions and 40 deletions
+32 -29
View File
@@ -41,15 +41,15 @@ type request struct {
}
type response struct {
Version int `json:"version"`
OK bool `json:"ok"`
Created bool `json:"created,omitempty"`
Replaced bool `json:"replaced,omitempty"`
Removed bool `json:"removed,omitempty"`
Found bool `json:"found,omitempty"`
Claimed bool `json:"claimed,omitempty"`
ContentBase64 string `json:"contentBase64,omitempty"`
Entries []safeio.PrivateDirectoryEntry `json:"entries,omitempty"`
Version int `json:"version"`
OK bool `json:"ok"`
Created bool `json:"created,omitempty"`
Replaced bool `json:"replaced,omitempty"`
Removed bool `json:"removed,omitempty"`
Found bool `json:"found,omitempty"`
Claimed bool `json:"claimed,omitempty"`
ContentBase64 string `json:"contentBase64,omitempty"`
Entries *[]safeio.PrivateDirectoryEntry `json:"entries,omitempty"`
}
// Run accepts exactly one strict JSON request on stdin and emits exactly one JSON response on
@@ -96,7 +96,7 @@ func fail(stderr io.Writer) int {
}
func execute(input request) (response, error) {
if input.Version != protocolVersion || !validDirectory(input.Directory) {
if input.Version != protocolVersion || !validDirectory(input.Directory) || !validOperationShape(input) {
return response{}, errInvalid
}
directory, err := storageDirectory(input.Root, input.Directory)
@@ -106,7 +106,7 @@ func execute(input request) (response, error) {
switch input.Operation {
case "create":
contents, err := decodeContents(input)
if err != nil || !digestFilename.MatchString(input.Filename) {
if err != nil {
return response{}, errInvalid
}
created, err := createPrivate(directory, input.Filename, contents)
@@ -115,9 +115,6 @@ func execute(input request) (response, error) {
}
return response{Version: protocolVersion, OK: true, Created: created}, nil
case "read":
if !digestFilename.MatchString(input.Filename) {
return response{}, errInvalid
}
contents, found, err := readPrivate(directory, input.Filename, recordMaximum(input.Directory))
if err != nil {
return response{}, errInvalid
@@ -125,7 +122,7 @@ func execute(input request) (response, error) {
return contentResponse(found, contents), nil
case "replace":
contents, err := decodeContents(input)
if err != nil || !digestFilename.MatchString(input.Filename) {
if err != nil {
return response{}, errInvalid
}
if err := safeio.ReplaceCanonicalRegular(filepath.Join(directory, input.Filename), contents, 0o600); err != nil {
@@ -133,9 +130,6 @@ func execute(input request) (response, error) {
}
return response{Version: protocolVersion, OK: true, Replaced: true}, nil
case "remove":
if !digestFilename.MatchString(input.Filename) && !claimFilename.MatchString(input.Filename) {
return response{}, errInvalid
}
removed, err := removePrivate(directory, input.Filename)
if err != nil {
return response{}, errInvalid
@@ -147,20 +141,14 @@ func execute(input request) (response, error) {
return response{}, errInvalid
}
for _, entry := range entries {
if !digestFilename.MatchString(entry.Name) && !claimFilename.MatchString(entry.Name) {
if !digestFilename.MatchString(entry.Name) && !(input.Directory == "oidc" && claimFilename.MatchString(entry.Name)) {
return response{}, errInvalid
}
}
return response{Version: protocolVersion, OK: true, Entries: entries}, nil
return response{Version: protocolVersion, OK: true, Entries: &entries}, nil
case "claim-consume":
if input.Directory != "oidc" || !digestFilename.MatchString(input.Filename) {
return response{}, errInvalid
}
return claimConsume(directory, input.Filename)
case "read-claim":
if input.Directory != "oidc" || !digestFilename.MatchString(input.Filename) {
return response{}, errInvalid
}
contents, found, err := safeio.ReadCanonicalPrivateClaim(
filepath.Join(directory, input.Filename),
filepath.Join(directory, asClaimFilename(input.Filename)),
@@ -171,9 +159,6 @@ func execute(input request) (response, error) {
}
return contentResponse(found, contents), nil
case "remove-claim":
if input.Directory != "oidc" || !digestFilename.MatchString(input.Filename) {
return response{}, errInvalid
}
removed, err := safeio.RemoveCanonicalPrivateClaim(
filepath.Join(directory, input.Filename),
filepath.Join(directory, asClaimFilename(input.Filename)),
@@ -187,6 +172,24 @@ func execute(input request) (response, error) {
}
}
func validOperationShape(input request) bool {
noContents := input.ContentBase64 == ""
switch input.Operation {
case "create", "replace":
return digestFilename.MatchString(input.Filename)
case "read":
return noContents && digestFilename.MatchString(input.Filename)
case "remove":
return noContents && (digestFilename.MatchString(input.Filename) || (input.Directory == "oidc" && claimFilename.MatchString(input.Filename)))
case "list":
return input.Filename == "" && noContents
case "claim-consume", "read-claim", "remove-claim":
return input.Directory == "oidc" && noContents && digestFilename.MatchString(input.Filename)
default:
return false
}
}
func contentResponse(found bool, contents []byte) response {
if !found {
return response{Version: protocolVersion, OK: true}