Skip to content
Closed
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
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ build:
cp bin/helm-ai-kernel bin/helm

test:
cd core && go test ./pkg/... ./cmd/release-permit-verify/... -count=1
cd core && go test ./pkg/... ./cmd/release-permit-verify/... ./cmd/promotion-permit-verify/... -count=1

test-cli:
cd core && go test ./cmd/helm-ai-kernel ./cmd/release-permit-verify -count=1
cd core && go test ./cmd/helm-ai-kernel ./cmd/release-permit-verify ./cmd/promotion-permit-verify -count=1

test-race:
cd core && go test ./pkg/... -count=1 -race
Expand Down
256 changes: 256 additions & 0 deletions core/cmd/promotion-permit-verify/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
package main

import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"os"
"sort"

"github.com/Mindburn-Labs/helm-ai-kernel/core/pkg/contracts"
"github.com/Mindburn-Labs/helm-ai-kernel/core/pkg/promotionpermit"
)

const maxInputBytes = 32 << 20

var promotionInputKeys = []string{
"schema", "target_environment", "release_manifest_ref", "release_manifest_generation",
"release_manifest_hash", "release_manifest_status", "platform_overlay_ref", "platform_overlay_hash",
"apps_overlay_ref", "apps_overlay_hash", "protected_environment", "apps_empty_intent",
}

var verificationContextKeys = []string{
"schema", "observed_at", "maximum_permit_ttl", "expected_policy_epoch", "emergency_fence",
"verdict_trust", "approval_trust", "approval_consumption_ref", "approval_authority",
"connector_release_trust", "current_connector_release", "permit", "dependency", "route_binding", "route_artifacts",
}

func main() {
if err := run(os.Args[1:], os.Stdout); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

func run(args []string, output io.Writer) error {
flags := flag.NewFlagSet("promotion-permit-verify", flag.ContinueOnError)
flags.SetOutput(io.Discard)
var envelopePath, promotionInputPath, promotionInputRef, releaseManifestPath string
var platformOverlayPath, appsOverlayPath, inputSchemaPath, verificationContextPath string
flags.StringVar(&envelopePath, "envelope", "", "immutable LaunchEffectAuthorizationEnvelope JSON")
flags.StringVar(&promotionInputPath, "promotion-input", "", "production promotion input JSON")
flags.StringVar(&promotionInputRef, "promotion-input-ref", "", "source-owned ref bound as promotion_permit_ref")
flags.StringVar(&releaseManifestPath, "release-manifest", "", "release manifest bytes bound by the promotion input")
flags.StringVar(&platformOverlayPath, "platform-overlay", "", "production platform overlay bytes bound by the promotion input")
flags.StringVar(&appsOverlayPath, "apps-overlay", "", "production apps overlay bytes bound by the promotion input")
flags.StringVar(&inputSchemaPath, "input-schema", "", "base-owned DEPLOY_PRODUCTION_ACTIVATE JSON schema; caller establishes provenance")
flags.StringVar(&verificationContextPath, "verification-context", "", "operator-supplied verification context; base-owned provenance must be established outside this CLI")
if err := flags.Parse(args); err != nil {
return err
}
if flags.NArg() != 0 {
return errors.New("positional arguments are not accepted")
}
for name, value := range map[string]string{
"--envelope": envelopePath, "--promotion-input": promotionInputPath,
"--promotion-input-ref": promotionInputRef, "--release-manifest": releaseManifestPath,
"--platform-overlay": platformOverlayPath, "--apps-overlay": appsOverlayPath,
"--input-schema": inputSchemaPath, "--verification-context": verificationContextPath,
} {
if value == "" {
return fmt.Errorf("%s is required", name)
}
}

var envelope contracts.LaunchEffectAuthorizationEnvelope
if _, err := decodeStrictFile(envelopePath, &envelope, nil); err != nil {
return fmt.Errorf("read launch authorization envelope: %w", err)
}
var input promotionpermit.Input
if _, err := decodeStrictFile(promotionInputPath, &input, promotionInputKeys); err != nil {
return fmt.Errorf("read promotion input: %w", err)
}
var verificationContext verificationContextInput
if _, err := decodeStrictFile(verificationContextPath, &verificationContext, verificationContextKeys); err != nil {
return fmt.Errorf("read verification context: %w", err)
}
releaseManifest, err := readRegularFile(releaseManifestPath)
if err != nil {
return fmt.Errorf("read release manifest: %w", err)
}
platformOverlay, err := readRegularFile(platformOverlayPath)
if err != nil {
return fmt.Errorf("read platform overlay: %w", err)
}
appsOverlay, err := readRegularFile(appsOverlayPath)
if err != nil {
return fmt.Errorf("read apps overlay: %w", err)
}
inputSchema, err := readRegularFile(inputSchemaPath)
if err != nil {
return fmt.Errorf("read input schema: %w", err)
}
launchContext, err := verificationContext.launchContext(envelope, inputSchema)
if err != nil {
return fmt.Errorf("build verification context: %w", err)
}
if err := promotionpermit.Verify(envelope, promotionpermit.VerificationContext{
PromotionInputRef: promotionInputRef,
PromotionInput: input,
ReleaseManifest: releaseManifest,
PlatformOverlay: platformOverlay,
AppsOverlay: appsOverlay,
Launch: launchContext,
}); err != nil {
return err
}
digest, err := input.Hash()
if err != nil {
return err
}
_, err = fmt.Fprintf(output, "production promotion preflight verified against supplied context: %s\n", digest)
return err
}

func decodeStrictFile(path string, destination any, requiredKeys []string) ([]byte, error) {
content, err := readRegularFile(path)
if err != nil {
return nil, err
}
if !json.Valid(content) {
return nil, errors.New("input is not exactly one valid JSON value")
}
if err := rejectDuplicateKeys(content); err != nil {
return nil, err
}
if len(requiredKeys) != 0 {
var object map[string]json.RawMessage
if err := json.Unmarshal(content, &object); err != nil {
return nil, errors.New("input must be a JSON object")
}
actual := make([]string, 0, len(object))
for key := range object {
actual = append(actual, key)
}
sort.Strings(actual)
expected := append([]string(nil), requiredKeys...)
sort.Strings(expected)
if fmt.Sprint(actual) != fmt.Sprint(expected) {
return nil, fmt.Errorf("input keys must be exactly %v", expected)
}
}
decoder := json.NewDecoder(bytes.NewReader(content))
decoder.DisallowUnknownFields()
if err := decoder.Decode(destination); err != nil {
return nil, err
}
var trailing any
if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) {
if err == nil {
return nil, errors.New("input contains more than one JSON value")
}
return nil, fmt.Errorf("read trailing data: %w", err)
}
return content, nil
}

func readRegularFile(path string) ([]byte, error) {
pathInfo, err := os.Lstat(path)
if err != nil {
return nil, err
}
if !pathInfo.Mode().IsRegular() || pathInfo.Size() > maxInputBytes {
return nil, fmt.Errorf("input must be a regular file no larger than %d bytes", maxInputBytes)
}
// #nosec G304 -- every path is an explicit offline verifier input.
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer func() { _ = file.Close() }()
openInfo, err := file.Stat()
if err != nil {
return nil, err
}
if !openInfo.Mode().IsRegular() || !os.SameFile(pathInfo, openInfo) {
return nil, errors.New("input changed while opening")
}
content, err := io.ReadAll(io.LimitReader(file, maxInputBytes+1))
if err != nil {
return nil, err
}
if len(content) > maxInputBytes {
return nil, fmt.Errorf("input exceeds %d bytes", maxInputBytes)
}
return content, nil
}

func rejectDuplicateKeys(content []byte) error {
decoder := json.NewDecoder(bytes.NewReader(content))
decoder.UseNumber()
if err := scanJSONValue(decoder); err != nil {
return err
}
if _, err := decoder.Token(); !errors.Is(err, io.EOF) {
if err == nil {
return errors.New("input contains more than one JSON value")
}
return err
}
return nil
}

func scanJSONValue(decoder *json.Decoder) error {
token, err := decoder.Token()
if err != nil {
return err
}
if token == nil {
return errors.New("JSON null values are not accepted")
}
delimiter, ok := token.(json.Delim)
if !ok {
return nil
}
switch delimiter {
case '{':
seen := map[string]struct{}{}
for decoder.More() {
keyToken, err := decoder.Token()
if err != nil {
return err
}
key, ok := keyToken.(string)
if !ok {
return errors.New("object key is not a string")
}
if _, exists := seen[key]; exists {
return fmt.Errorf("duplicate JSON key %q", key)
}
seen[key] = struct{}{}
if err := scanJSONValue(decoder); err != nil {
return err
}
}
end, err := decoder.Token()
if err != nil || end != json.Delim('}') {
return errors.New("JSON object did not end with }")
}
case '[':
for decoder.More() {
if err := scanJSONValue(decoder); err != nil {
return err
}
}
end, err := decoder.Token()
if err != nil || end != json.Delim(']') {
return errors.New("JSON array did not end with ]")
}
default:
return fmt.Errorf("unexpected JSON delimiter %q", delimiter)
}
return nil
}
105 changes: 105 additions & 0 deletions core/cmd/promotion-permit-verify/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// quantum_posture: these verifier tests exercise existing classical Ed25519
// trust inputs only; they add no post-quantum assurance claim.
package main

import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"time"

"github.com/Mindburn-Labs/helm-ai-kernel/core/pkg/contracts"
"github.com/Mindburn-Labs/helm-ai-kernel/core/pkg/promotionpermit"
)

func TestDecodeStrictPromotionInputRejectsAmbiguousJSON(t *testing.T) {
encoded, err := json.Marshal(promotionpermit.Input{
Schema: promotionpermit.InputSchemaV1, TargetEnvironment: "production",
ReleaseManifestRef: "release:1", ReleaseManifestGeneration: 1,
ReleaseManifestHash: "sha256:" + strings.Repeat("a", 64), ReleaseManifestStatus: promotionpermit.ReleaseManifestStatusProductionCandidate,
PlatformOverlayRef: "platform:1", PlatformOverlayHash: "sha256:" + strings.Repeat("b", 64),
AppsOverlayRef: "apps:1", AppsOverlayHash: "sha256:" + strings.Repeat("c", 64),
ProtectedEnvironment: "production", AppsEmptyIntent: false,
})
if err != nil {
t.Fatal(err)
}
valid := string(encoded)
tests := []struct {
name string
content string
want string
}{
{name: "duplicate", content: strings.Replace(valid, `"schema":`, `"schema":"forged","schema":`, 1), want: `duplicate JSON key "schema"`},
{name: "unknown", content: strings.TrimSuffix(valid, "}") + `,"unexpected":true}`, want: "input keys must be exactly"},
{name: "missing explicit bool", content: strings.Replace(valid, `,"apps_empty_intent":false`, "", 1), want: "input keys must be exactly"},
{name: "null explicit bool", content: strings.Replace(valid, `"apps_empty_intent":false`, `"apps_empty_intent":null`, 1), want: "JSON null values are not accepted"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var input promotionpermit.Input
_, err := decodeStrictFile(writeFixture(t, test.content), &input, promotionInputKeys)
if err == nil || !strings.Contains(err.Error(), test.want) {
t.Fatalf("decodeStrictFile() error = %v, want substring %q", err, test.want)
}
})
}
}

func TestRunRejectsDuplicateEnvelopeBeforeTrustResolution(t *testing.T) {
path := writeFixture(t, `{"effect_id":"DEPLOY_PRODUCTION_ACTIVATE","effect_id":"PROVIDER_PROVISION"}`)
err := run([]string{
"--envelope", path, "--promotion-input", path, "--promotion-input-ref", "promotion:1",
"--release-manifest", path, "--platform-overlay", path, "--apps-overlay", path,
"--input-schema", path, "--verification-context", path,
}, &strings.Builder{})
if err == nil || !strings.Contains(err.Error(), `duplicate JSON key "effect_id"`) {
t.Fatalf("run() error = %v, want duplicate envelope rejection", err)
}
}

func TestReadRegularFileRejectsSymlink(t *testing.T) {
target := writeFixture(t, "trusted")
link := filepath.Join(t.TempDir(), "input")
if err := os.Symlink(target, link); err != nil {
t.Skipf("create symlink: %v", err)
}
if _, err := readRegularFile(link); err == nil || !strings.Contains(err.Error(), "regular file") {
t.Fatalf("readRegularFile() error = %v, want symlink rejection", err)
}
}

func TestVerificationContextRejectsMalformedVerdictKey(t *testing.T) {
inputs := verificationContextInput{
Schema: verificationContextSchemaV1, ObservedAt: time.Date(2026, 8, 6, 12, 0, 0, 0, time.UTC),
MaximumPermitTTL: "1m", ExpectedPolicyEpoch: "epoch-1", ApprovalConsumptionRef: "consumption:1",
VerdictTrust: trustKey{PublicKey: "ed25519:" + strings.Repeat("0", 62) + "zz"},
}
_, err := inputs.launchContext(contracts.LaunchEffectAuthorizationEnvelope{}, []byte(`{"type":"object"}`))
if err == nil || !strings.Contains(err.Error(), "verification context verdict key") {
t.Fatalf("launchContext() error = %v, want malformed verdict key rejection", err)
}
}

func TestTrustedPermitRejectsWrongEffect(t *testing.T) {
now := time.Date(2026, 8, 6, 12, 0, 0, 0, time.UTC)
permit := permitBinding{
EffectID: contracts.EffectTypeProviderProvision, EffectOrdinal: 1, SingleUse: true,
PermitIssuedAt: now, PermitExpiry: now.Add(time.Minute), KernelVerdictIssuedAt: now,
KernelVerdictExpiry: now.Add(time.Minute), DispatchDeadline: now.Add(30 * time.Second),
}
if err := permit.validate(); err == nil || !strings.Contains(err.Error(), contracts.EffectTypeDeployProductionActivate) {
t.Fatalf("validate() error = %v, want effect rejection", err)
}
}

func writeFixture(t *testing.T, content string) string {
t.Helper()
path := filepath.Join(t.TempDir(), "fixture")
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
t.Fatal(err)
}
return path
}
Loading
Loading