Skip to content

[High] policyyaml loader accepts shapes the upstream loader rejects; port+ports silently drops port; ToSDK can never fail #19

Description

@clcollins

Severity: High (policy is a security boundary; the client must not accept a policy the reference loader would refuse). Lint/serialize/loading defects are in item 25.

Loader accepts what the Rust reference loader (crates/openshell-policy, v0.0.116) refuses

  • rules: [{}] → L7Rule{Allow: &L7Allow{}} — an allow rule with no constraints. Upstream L7RuleDef{allow: L7AllowDef} is required and fails with "missing field allow". Whether the server treats an empty L7Allow as match-all could not be confirmed from the SDK; the client accepting a shape the reference loader refuses is the dangerous direction regardless.
  • binaries: [{harness: true}] → Path: ""; credential_binding: {} → Provider: ""; missing version → 0; an empty file → valid policy. pkg/policyyaml/types.go:10 says "version is required" — nothing enforces it.
  • port: 443, ports: [80] → only 80 (pkg/policyyaml/toproto.go:124-131) although types.go:51 documents them as mutually exclusive.
  • ToSDK can currently never fail: middlewaresToSDK (toproto.go:242) has a single return out, nil, so the err branch at toproto.go:47-50 is unreachable.

Fix

Post-decode required-field validation (or UnmarshalJSON on L7RuleDef that fails when allow is absent, mirroring serde's "missing field"); make port+ports an error; give ToSDK real error paths and use them.

Test quality (owned here)

pkg/policyyaml/coverage_test.go is padding by name and content (TestParse_InlineJSONError feeds a chan to hit a marshal branch; four nil-path tests). TestToSDK_FullEndpointFields asserts 7 of 25 endpoint fields; Path, WebsocketCredentialRewrite, RequestBodyCredentialRewrite, AllowUninspectedCredentials, PersistedQueries, GraphqlMaxBodyBytes, GraphqlPersistedQueries, SigningService, SigningRegion, StrictToolNames have no assertion anywhere. TestFromSDK_RoundTripStructure checks ~6 fields. 13 tests discard the Parse error (p, _ := Parse(...): toproto_test.go:63,83,106,129,153,179,201,228,249,268; serialize_test.go:84,108,136) so regressions surface as nil-deref panics. Nested unknown-field rejection (alow, deny_rulez, {any:[a], glob:b}, {any: 5}, duplicate keys) works but is untested; denyFromSDK is 0% covered, queryMapFromSDK 33%. pkg/api/v1alpha1/v1alpha1_test.go:85 covers only policy/policyFile mutual exclusion. Every test is a single case; no table tests.

Acceptance

  • Golden fixture (the content-guard example, plan Appendix B) → ToSDK DeepEqual against a hand-written full types.SandboxPolicy covering all 25 endpoint fields.
  • Table tests: each rejected shape above → error naming the field; nested unknown keys → error.
  • All Parse errors in tests checked.

Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions