fix(cli): admit a mapping on version alone only as the policy version this build reads - #283
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
There was a problem hiding this comment.
Code Review
This pull request addresses issue #240 by introducing a new validation guard, admitted_only_by_an_unknown_version, which ensures that files containing a version key with an unsupported version number (such as docker-compose files) are rejected when version is the only recognized policy field declared. This prevents foreign configurations from being loaded as valid empty policies. The changes also include updated tests and documentation. The review feedback correctly identifies that several cited line ranges in the documentation should be updated to start at the first line of the respective function's doc comments to preserve context.
…e the forward-compat test doc Review round on #283. The quoted spelling with nothing beside it was asserted only of the rule and the loader separately; it now goes through all three commands, so the order of the guards in load_policy is what the test sees. The forward-compat unit test's doc says which rule its sentence is about, since the read as a whole now refuses its first fixture on the version value. A tag on the version value joins the admitted fixtures, measured against the loader. Wiki anchors start on each item's doc comment, and the residual names a concrete file class.
…an a second literal Greptile on #283: asserting the constant against another 1 detects no drift. The test now parses policies/agent.yaml — the artifact a new policy is copied from — and requires its version to be POLICY_VERSION, so a schema bump that lands in the example fails here until the constant follows. The wiki anchor for the value rule moves with the const doc.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request addresses issue #240 by introducing a third validation guard in load_policy (admitted_only_by_an_unknown_version). This guard ensures that if a YAML mapping's only recognized policy key is version, it is only admitted if the version value matches the supported POLICY_VERSION (1). This prevents files like docker-compose.yml (which often start with version: 3) from being incorrectly loaded as valid empty policies, while still allowing minimal policies containing only version: 1 to load. The PR also includes comprehensive unit and integration tests to verify this behavior, updates the rejected findings log, and updates the documentation in the wiki and agent memory. There are no review comments provided, so I have no feedback to evaluate.
|
CodSpeed record for Verdict as posted at 02:45:55Z: "Merging this PR will degrade performance by 36.59%", 2 regressed, 16 untouched, comparing
The third Follow-up, 02:49:38Z. After a rebase onto |
…on this build reads `load_policy` admitted any mapping that named `version`, and `version` is the one recognised key other formats use: an unquoted `version: 3` at the top of a docker-compose.yml loaded as a 0-rule policy whose source `gateway --config` served at `GET /api/policy`. Dropping `version` from the name rule would refuse a file containing only `version: 1`, a policy the gateway starts on, so the read now refuses on the value instead: when `version` is the only recognised key, it admits the document only as `POLICY_VERSION` (1). Beside `egress`, `endpoints` or `rules` the value is not consulted, so forward-compatibility of fields and serde's own diagnosis of a mistyped value are unchanged. Pinned on the binary in both directions: the compose file is refused on all three commands with nothing from the file in the message, and `version: 1` alone still validates and starts the gateway. The wiki paragraph that recorded the residual now records the rule, and llms-full.txt is regenerated. Refs #240
…by the version value rule (#240)
…e the forward-compat test doc Review round on #283. The quoted spelling with nothing beside it was asserted only of the rule and the loader separately; it now goes through all three commands, so the order of the guards in load_policy is what the test sees. The forward-compat unit test's doc says which rule its sentence is about, since the read as a whole now refuses its first fixture on the version value. A tag on the version value joins the admitted fixtures, measured against the loader. Wiki anchors start on each item's doc comment, and the residual names a concrete file class.
…an a second literal Greptile on #283: asserting the constant against another 1 detects no drift. The test now parses policies/agent.yaml — the artifact a new policy is copied from — and requires its version to be POLICY_VERSION, so a schema bump that lands in the example fails here until the constant follows. The wiki anchor for the value rule moves with the const doc.
e33a0f3 to
2b1db08
Compare
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2b1db08978
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".



What
load_policyadmitted any mapping that namedversion, andversionis the one of the fourrecognised keys (
version,egress,endpoints,rules) that other config formats also use. Anunquoted
version: 3at the top of adocker-compose.yml— the ordinary v2/v3-era spelling, andexactly what a
--configmistyped one file over in a deploy repository lands on — was thereforeadmitted by name and loaded as a 0-rule policy.
Reproduced on the binary at
0c350c2(currentmain) before the change, with a throwaway value:version: 3+services:with an inlinePOSTGRES_PASSWORD:policy is valid (0 rules, 0 endpoints), exit 0version: 1alonepolicy is valid (0 rules, 0 endpoints), exit 0Scale
The same bounds #220 recorded, and the write-up should not say more than they allow. Under
gateway --configthe admitted file's whole text becameAppState.policy_yamlandGET /api/policyserved it verbatim, so the compose file's inline environment values were readable there — but that
route is behind the management-token
route_layer(#173), so the audience was mgmt-token /dashboard-session holders rather than the network; and the resulting policy was
egress.default: denywith no rules, so traffic handling stayed fail-closed throughout. A defect to schedule, whichis what #240 filed it as.
How
Refuse on a property of the value, not on the presence of the name.
load_policyruns a thirdguard after the name rule,
admitted_only_by_an_unknown_version: whenversionis the onlyrecognised key a mapping declares, it admits the document only as the integer
POLICY_VERSION(1),the policy schema version this build reads. Any other value — compose's
2and3, the0anabsent
versiondefaults to, or a quoted/fractional spelling — is refused for the path in the samecontent-free words the other two guards use.
Why that shape and not the obvious one:
versionfrom the name rule is not a fix. It refuses a file containing onlyversion: 1, a policy the gateway starts on today, and refusing a policy the gateway runs costsan operator an outage rather than a diagnosis.
a_policy_declaring_only_its_version_still_loadspins that on the binary, through
validateand through the gateway; it fails under that fix(verified by making the change and watching it fail).
versionis the sole ticket. Besideegress,endpointsor
rulesthe value is not consulted, so forward-compatibility of fields is exactly as core: a mapping-shaped secrets file loads as a valid 0-rule policy and is served at GET /api/policy #220left it (
a_policy_carrying_an_unknown_field_still_loads, withversion: 2besideegress,still runs green through the binary), and a mistyped
version: "1.0"aboverules:stillreaches serde's own quoting of those three characters with a line and column — the documented
bound, unchanged. Only a file whose sole recognised key is a mistyped
versiongets thecontent-free refusal instead.
Policyis untouched. Nodeny_unknown_fields, no field change, no new dependency, nodecide()change — so this stays off thecrates/AGENTS.mdAsk first list and out ofTD-001's TS-type / JSON Schema sync.
POLICY_VERSIONlives in the CLI besidePOLICY_FIELDSfor the same reason that list does:
honmoon-coredeclares no such constant (the field is aplain
u32, the schema says only>= 1), andonly_this_builds_policy_version_is_an_admission_ticketreads the shipped
policies/agent.yamland requires itsversionto equal the constant, so aschema bump that lands in the example fails a test rather than refusing the bumped file at a deploy.
What moves, stated
One file class besides compose loaded before and is refused now: a mapping that declares a schema
version this build does not implement and nothing this build reads at the top level (
version: 2with only unknown siblings). It loaded as deny-all with no rules — a policy this build could not run
as written, #220's class — and the refusal names the version rule, so a rollback that lands there is
a diagnosis.
an_unknown_sibling_of_a_recognised_key_still_loadskeeps itsversion: 2\ntelemetry:fixture: it is a statement about the name rule, which is unchanged and its doc comment now says so.
The remaining bound is a foreign file that opens with an unquoted
version: 1and declares nothingelse honmoon reads. It is admitted, and nothing about that line can tell it from the minimal policy;
the wiki states it where the residual used to be stated.
Tests
Red first, verified rather than assumed:
no_command_accepts_a_compose_file_admitted_only_by_its_version(binary, all three commands, on thecompose file and on a quoted
version: "1.0"with nothing beside it) anda_compose_file_is_refused_and_a_version_one_policy_is_not/only_this_builds_policy_version_is_an_admission_ticket(unit) fail with the new guard neuteredin place.
a_policy_declaring_only_its_version_still_loadsfails under the rejected fix (versiondroppedfrom
POLICY_FIELDS).version_alone_admits_a_file_no_operator_wrote_as_a_policy, which pinned the residual, isdeleted — its own doc comment asked for exactly that once the ticket was narrowed deliberately.
No other existing test was edited or weakened;
no_command_takes_itgained asaysargument sothe compose case can assert which rule answered.
Docs
wiki/getting-started/policy-authoring.md— the residual paragraph becomes the rule, the"two things" the read says becomes three, the
version: "1.0"bound is qualified to the case itstill holds for, and the
main.rsanchors are re-resolved against this head.wiki/llms-full.txtregenerated (
scripts/check-wiki-bundle-current.tsholds it byte-identical). The securityreviewer's memory note that recorded the compose case as tracked-in-#240 now records it as closed.
Gates
cargo fmt --all --check,cargo clippy --workspace --all-targets -- -D warnings,HONMOON_REQUIRE_ENFORCEMENT=1 cargo test --workspace,bun run lint && bun run typecheck && bun test— all green locally.Closes #240