From 3c8b3463461baa647add238df4bbdfbbb4e88053 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 28 Jun 2026 16:05:43 -0400 Subject: [PATCH 1/7] Add SCOPE-D delivery envelope validation --- internal/delivery/envelope.go | 97 +++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 internal/delivery/envelope.go diff --git a/internal/delivery/envelope.go b/internal/delivery/envelope.go new file mode 100644 index 0000000..6846834 --- /dev/null +++ b/internal/delivery/envelope.go @@ -0,0 +1,97 @@ +// Package delivery validates policy-gated SCOPE-D delivery envelopes before +// they are accepted by CloudShell Fog as edge assurance work. +package delivery + +import ( + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "strings" +) + +// DeliveryEnvelope describes a non-executing SCOPE-D work package that may be +// staged for operator review at a CloudShell Fog edge node. +type DeliveryEnvelope struct { + SchemaVersion string `json:"schemaVersion"` + EnvelopeID string `json:"envelopeId"` + SourceSystem string `json:"sourceSystem"` + Purpose string `json:"purpose"` + ArtifactRefs []string `json:"artifactRefs"` + RequiredPolicyRefs []string `json:"requiredPolicyRefs"` + OperatorApprovalRequired bool `json:"operatorApprovalRequired"` + ExecutionAllowed bool `json:"executionAllowed"` + ExecutionPerformed bool `json:"executionPerformed"` + NetworkAccessAllowed bool `json:"networkAccessAllowed"` + MutationAllowed bool `json:"mutationAllowed"` + CredentialAccessAllowed bool `json:"credentialAccessAllowed"` + PayloadDeliveryAllowed bool `json:"payloadDeliveryAllowed"` + ReceiptHash string `json:"receiptHash"` +} + +// Sentinel validation errors. +var ( + ErrUnsupportedSchema = errors.New("unsupported delivery envelope schema") + ErrInvalidEnvelopeID = errors.New("invalid delivery envelope id") + ErrInvalidSourceSystem = errors.New("invalid source system") + ErrInvalidPurpose = errors.New("invalid delivery purpose") + ErrMissingArtifacts = errors.New("missing artifact references") + ErrMissingPolicies = errors.New("missing required policy references") + ErrOperatorApprovalMissing = errors.New("operator approval requirement missing") + ErrUnsafeCapability = errors.New("unsafe delivery capability requested") + ErrInvalidReceiptHash = errors.New("invalid receipt hash") +) + +// Validate enforces CloudShell Fog's default posture for SCOPE-D delivery: +// reviewable, receipt-backed, policy-gated, and non-executing. +func Validate(envelope DeliveryEnvelope) error { + if envelope.SchemaVersion != "0.1.0" { + return fmt.Errorf("%w: %s", ErrUnsupportedSchema, envelope.SchemaVersion) + } + if !strings.HasPrefix(envelope.EnvelopeID, "cloudshell-fog-delivery:") { + return fmt.Errorf("%w: %s", ErrInvalidEnvelopeID, envelope.EnvelopeID) + } + if envelope.SourceSystem != "scope-d" { + return fmt.Errorf("%w: %s", ErrInvalidSourceSystem, envelope.SourceSystem) + } + if envelope.Purpose != "edge_assurance_review" && envelope.Purpose != "policy_gated_delivery_review" { + return fmt.Errorf("%w: %s", ErrInvalidPurpose, envelope.Purpose) + } + if len(envelope.ArtifactRefs) == 0 { + return ErrMissingArtifacts + } + if len(envelope.RequiredPolicyRefs) == 0 { + return ErrMissingPolicies + } + if !envelope.OperatorApprovalRequired { + return ErrOperatorApprovalMissing + } + if envelope.ExecutionAllowed || envelope.ExecutionPerformed || envelope.NetworkAccessAllowed || envelope.MutationAllowed || envelope.CredentialAccessAllowed || envelope.PayloadDeliveryAllowed { + return ErrUnsafeCapability + } + if !strings.HasPrefix(envelope.ReceiptHash, "sha256:") || len(strings.TrimPrefix(envelope.ReceiptHash, "sha256:")) != 64 { + return fmt.Errorf("%w: %s", ErrInvalidReceiptHash, envelope.ReceiptHash) + } + return nil +} + +// ComputeReceiptHash returns a deterministic hash over the policy-significant +// fields. It intentionally excludes ReceiptHash itself. +func ComputeReceiptHash(envelope DeliveryEnvelope) string { + material := strings.Join([]string{ + envelope.SchemaVersion, + envelope.EnvelopeID, + envelope.SourceSystem, + envelope.Purpose, + strings.Join(envelope.ArtifactRefs, ","), + strings.Join(envelope.RequiredPolicyRefs, ","), + fmt.Sprintf("approval=%t", envelope.OperatorApprovalRequired), + fmt.Sprintf("executionAllowed=%t", envelope.ExecutionAllowed), + fmt.Sprintf("networkAccessAllowed=%t", envelope.NetworkAccessAllowed), + fmt.Sprintf("mutationAllowed=%t", envelope.MutationAllowed), + fmt.Sprintf("credentialAccessAllowed=%t", envelope.CredentialAccessAllowed), + fmt.Sprintf("payloadDeliveryAllowed=%t", envelope.PayloadDeliveryAllowed), + }, "|") + sum := sha256.Sum256([]byte(material)) + return "sha256:" + hex.EncodeToString(sum[:]) +} From 4ab038665b4456183bd3e7a6675b7aecd655a279 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 28 Jun 2026 16:06:11 -0400 Subject: [PATCH 2/7] Add SCOPE-D delivery envelope tests --- internal/delivery/envelope_test.go | 86 ++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 internal/delivery/envelope_test.go diff --git a/internal/delivery/envelope_test.go b/internal/delivery/envelope_test.go new file mode 100644 index 0000000..7f0f49c --- /dev/null +++ b/internal/delivery/envelope_test.go @@ -0,0 +1,86 @@ +package delivery + +import ( + "errors" + "testing" +) + +func validEnvelope() DeliveryEnvelope { + envelope := DeliveryEnvelope{ + SchemaVersion: "0.1.0", + EnvelopeID: "cloudshell-fog-delivery:scope-d-demo-001", + SourceSystem: "scope-d", + Purpose: "edge_assurance_review", + ArtifactRefs: []string{"scope-d://cyber-graph-export/demo", "scope-d://detection-candidate-export/demo"}, + RequiredPolicyRefs: []string{"policyfabric://approval/operator-review-required"}, + OperatorApprovalRequired: true, + ExecutionAllowed: false, + ExecutionPerformed: false, + NetworkAccessAllowed: false, + MutationAllowed: false, + CredentialAccessAllowed: false, + PayloadDeliveryAllowed: false, + } + envelope.ReceiptHash = ComputeReceiptHash(envelope) + return envelope +} + +func TestValidateAcceptsSafeScopeDEnvelope(t *testing.T) { + if err := Validate(validEnvelope()); err != nil { + t.Fatalf("expected valid envelope, got %v", err) + } +} + +func TestValidateRejectsExecutionAllowed(t *testing.T) { + envelope := validEnvelope() + envelope.ExecutionAllowed = true + envelope.ReceiptHash = ComputeReceiptHash(envelope) + if err := Validate(envelope); !errors.Is(err, ErrUnsafeCapability) { + t.Fatalf("expected unsafe capability error, got %v", err) + } +} + +func TestValidateRejectsNetworkMutationCredentialAndPayloadCapabilities(t *testing.T) { + cases := map[string]func(*DeliveryEnvelope){ + "network": func(e *DeliveryEnvelope) { e.NetworkAccessAllowed = true }, + "mutation": func(e *DeliveryEnvelope) { e.MutationAllowed = true }, + "credential": func(e *DeliveryEnvelope) { e.CredentialAccessAllowed = true }, + "payload": func(e *DeliveryEnvelope) { e.PayloadDeliveryAllowed = true }, + } + for name, mutate := range cases { + t.Run(name, func(t *testing.T) { + envelope := validEnvelope() + mutate(&envelope) + envelope.ReceiptHash = ComputeReceiptHash(envelope) + if err := Validate(envelope); !errors.Is(err, ErrUnsafeCapability) { + t.Fatalf("expected unsafe capability error, got %v", err) + } + }) + } +} + +func TestValidateRequiresOperatorApproval(t *testing.T) { + envelope := validEnvelope() + envelope.OperatorApprovalRequired = false + envelope.ReceiptHash = ComputeReceiptHash(envelope) + if err := Validate(envelope); !errors.Is(err, ErrOperatorApprovalMissing) { + t.Fatalf("expected operator approval error, got %v", err) + } +} + +func TestValidateRequiresPolicyRefs(t *testing.T) { + envelope := validEnvelope() + envelope.RequiredPolicyRefs = nil + envelope.ReceiptHash = ComputeReceiptHash(envelope) + if err := Validate(envelope); !errors.Is(err, ErrMissingPolicies) { + t.Fatalf("expected missing policies error, got %v", err) + } +} + +func TestValidateRequiresReceiptHashShape(t *testing.T) { + envelope := validEnvelope() + envelope.ReceiptHash = "sha256:not-a-real-hash" + if err := Validate(envelope); !errors.Is(err, ErrInvalidReceiptHash) { + t.Fatalf("expected invalid receipt hash error, got %v", err) + } +} From 6b05fe469ccd8a99d8b624e2580db0126e3857ba Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 28 Jun 2026 16:06:38 -0400 Subject: [PATCH 3/7] Document SCOPE-D delivery envelopes --- docs/scope-d-delivery-envelopes.md | 96 ++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 docs/scope-d-delivery-envelopes.md diff --git a/docs/scope-d-delivery-envelopes.md b/docs/scope-d-delivery-envelopes.md new file mode 100644 index 0000000..74be752 --- /dev/null +++ b/docs/scope-d-delivery-envelopes.md @@ -0,0 +1,96 @@ +# SCOPE-D Delivery Envelopes + +CloudShell Fog participates in the SCOPE-D platform as an authorized edge operator bastion and mesh assurance node. + +This document defines the first delivery boundary between SCOPE-D and CloudShell Fog. + +## Role + +CloudShell Fog is not a scanner and not an autonomous execution agent in this flow. It accepts reviewable, policy-gated SCOPE-D delivery envelopes for edge assurance workflows. + +The initial envelope validator accepts only non-executing review packages. + +## Accepted source + +`sourceSystem` must be: + +```text +scope-d +``` + +## Accepted purposes + +- `edge_assurance_review` +- `policy_gated_delivery_review` + +## Required fields + +A delivery envelope must include: + +- schema version; +- envelope id; +- source system; +- purpose; +- artifact references; +- required policy references; +- operator approval requirement; +- non-execution flags; +- receipt hash. + +## Prohibited capabilities in v0.1 + +The validator rejects any envelope that requests: + +- execution; +- prior execution; +- network access; +- mutation; +- credential access; +- payload delivery. + +## Expected SCOPE-D artifact references + +SCOPE-D should reference artifacts such as: + +- intelligence enrichment export; +- detection candidate export; +- cyber graph export; +- operator case bundle; +- client assurance report; +- PolicyFabric approval record. + +## Validation path + +The Go package is: + +```text +internal/delivery +``` + +The validator is: + +```go +Validate(envelope DeliveryEnvelope) error +``` + +The receipt helper is: + +```go +ComputeReceiptHash(envelope DeliveryEnvelope) string +``` + +## Test path + +```bash +go test ./internal/delivery +``` + +## Next slices + +1. Add API endpoint for staging delivery envelopes. +2. Persist staged envelopes in the existing session/audit subsystem. +3. Add PolicyFabric signature verification. +4. Add SCOPE-D receipt verification. +5. Add edge sync status for Noetica. +6. Add read-only operator review endpoint. +7. Add deployment receipt generation after approved future delivery modes. From bbd02bdcee96ed834b09685d6cc4c2c691552398 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 28 Jun 2026 16:19:31 -0400 Subject: [PATCH 4/7] Simplify delivery envelope vet surface --- internal/delivery/envelope.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/delivery/envelope.go b/internal/delivery/envelope.go index 6846834..68f03ca 100644 --- a/internal/delivery/envelope.go +++ b/internal/delivery/envelope.go @@ -46,16 +46,16 @@ var ( // reviewable, receipt-backed, policy-gated, and non-executing. func Validate(envelope DeliveryEnvelope) error { if envelope.SchemaVersion != "0.1.0" { - return fmt.Errorf("%w: %s", ErrUnsupportedSchema, envelope.SchemaVersion) + return ErrUnsupportedSchema } if !strings.HasPrefix(envelope.EnvelopeID, "cloudshell-fog-delivery:") { - return fmt.Errorf("%w: %s", ErrInvalidEnvelopeID, envelope.EnvelopeID) + return ErrInvalidEnvelopeID } if envelope.SourceSystem != "scope-d" { - return fmt.Errorf("%w: %s", ErrInvalidSourceSystem, envelope.SourceSystem) + return ErrInvalidSourceSystem } if envelope.Purpose != "edge_assurance_review" && envelope.Purpose != "policy_gated_delivery_review" { - return fmt.Errorf("%w: %s", ErrInvalidPurpose, envelope.Purpose) + return ErrInvalidPurpose } if len(envelope.ArtifactRefs) == 0 { return ErrMissingArtifacts @@ -70,7 +70,7 @@ func Validate(envelope DeliveryEnvelope) error { return ErrUnsafeCapability } if !strings.HasPrefix(envelope.ReceiptHash, "sha256:") || len(strings.TrimPrefix(envelope.ReceiptHash, "sha256:")) != 64 { - return fmt.Errorf("%w: %s", ErrInvalidReceiptHash, envelope.ReceiptHash) + return ErrInvalidReceiptHash } return nil } From ba7bf90112304018f4dbded0d82377cc6d32bab8 Mon Sep 17 00:00:00 2001 From: Michael Heller Date: Sun, 28 Jun 2026 19:31:20 -0400 Subject: [PATCH 5/7] fix(deps): add missing go.sum entry for github.com/evanphx/json-patch k8s.io/client-go/testing imports github.com/evanphx/json-patch v4.12.0+incompatible. The go.sum entry was absent, causing `go vet` and `go test` to fail in CI with "missing go.sum entry" errors. Hashes sourced from sum.golang.org and cross-verified against the k8s.io/client-go v0.30.2 transitive dependency graph. Co-Authored-By: Claude Sonnet 4.6 --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index 1784dab..7ae7469 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= From abe3235fa9afc05b83a6687e5005a1c129a612a8 Mon Sep 17 00:00:00 2001 From: Michael Heller Date: Sun, 28 Jun 2026 19:53:00 -0400 Subject: [PATCH 6/7] fix(go): add missing evanphx/json-patch indirect dep to go.mod go mod tidy requires this transitive dependency of k8s.io/client-go@v0.30.2 to be listed in go.mod. The go.sum hashes were already added; go.mod was missing the indirect require entry, causing CI to report "updates to go.mod needed". Co-Authored-By: Claude Sonnet 4.6 --- go.mod | 1 + 1 file changed, 1 insertion(+) diff --git a/go.mod b/go.mod index 6f2befa..c0695f5 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/go-jose/go-jose/v4 v4.0.1 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect From 150aafb1138e2f96aa314b02fc7b2b9719c57756 Mon Sep 17 00:00:00 2001 From: Michael Heller Date: Sun, 28 Jun 2026 20:50:01 -0400 Subject: [PATCH 7/7] fix(go): add pkg/errors transitive dep required by evanphx/json-patch github.com/evanphx/json-patch imports github.com/pkg/errors; both go.sum hashes and the go.mod indirect entry were missing, causing the CI "Go build & test" check to fail with a missing go.sum entry error. Co-Authored-By: Claude Sonnet 4.6 --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index c0695f5..a66d1e9 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/net v0.23.0 // indirect diff --git a/go.sum b/go.sum index 7ae7469..010a54b 100644 --- a/go.sum +++ b/go.sum @@ -79,6 +79,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE=