Skip to content

[Medium] policyyaml: lint gives false confidence, load→serialize→load is lossy, multi-document YAML silently truncated, no size limit, no glob validation #25

Description

@clcollins

Severity: Medium. Loader strictness (the security-relevant half) is item 14; this item is the UX/fidelity half.

Lint gives false confidence

pkg/policyyaml/lint.go:22-30 claims to "reimplement the server-side validate_sandbox_policy" then admits a subset. Verified: host: "*" + access: full + enforcement: audit + allow_uninspected_credentials: true + read_write: ["/"] yields only the / finding; version: 1 alone is "no problems found" and TestPolicyLint_Clean enshrines that. No checks on protocol/tls/enforcement/access/compatibility enum values, empty host, empty allow rule, allowed_ips syntax. Two bugs: lint.go:82 uses strings.Contains(path, "..") (flags /opt/a..b while the message says "component"); lint.go:195 hardcodes endpoint index 0 for every endpoint, and the message mentions rules/deny_rules but only Access is checked (:194). Fix: either port the documented rule set from crates/openshell-policy (lib.rs, l7_validate.rs at v0.0.116, plan Appendix B.2B) or make the "subset" caveat loud in policy lint --help; fix both bugs regardless.

No client-side pattern validation at all

pkg/policyyaml/matchers.go is purely the untagged QueryMatcher/ParamMatcher shape decoder; there is no glob/path/regex matching or syntax validation in the client. That means no divergent-semantics risk versus the server, but also that lint cannot catch a malformed glob. State this in the policy lint help and README so nobody assumes patterns are validated locally.

Lossy round-trip

pkg/policyyaml/serialize.go:69-117 never emits json_rpc.max_body_bytes/mcp.max_body_bytes (only GraphqlMaxBodyBytes at :83; JSONRPCMaxBodyBytes: 7000 → 0 after reparse); :157-162 turns {any: []} into glob "" (semantics change); insertNested (:177-195) collides on flat keys a and a.b (the leaf a is overwritten); the tool restoration promised at :11-14 is not implemented. TestSerialize_StructuralYAML checks four substrings; TestFromSDK_RoundTripStructure checks ~6 fields; nothing asserts DeepEqual on a full fixture. Byte-parity with upstream --policy-only (indentless sequences, quoting) is a separate acknowledged gap (serialize.go:213-217, item 18).

Loading

pkg/policyyaml/load.go:58 sigs.k8s.io/yaml.UnmarshalStrict reads only the first YAML document; version: 1\n---\nversion: 2\nbogus: 1 parses as version 1 with no error. No size limit on ReadFile (load.go:23, internal/cli/policy.go:27). policy lint has no stdin - support, unlike -f.

Acceptance

  • load→serialize→load DeepEqual on the content-guard fixture (plan Appendix B) including max_body_bytes and empty any.
  • Multi-document policy → error naming the second document; > N MiB file → error.
  • Lint table test: each dangerous shape above produces a finding with the correct endpoint index; /opt/a..b produces none.

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