Repository navigation
security: the ODCS and plugin-extension checks let a trailing newline through a pattern - #811
Merged
Merged
Conversation
… through a pattern The FLUID fix (schema_validator, #799) left six JSON Schema checks on jsonschema's own pattern keyword, Python's re.search, whose $ also matches before a final newline: - forge/core/artifact_validators.py (fluid validate-artifacts: ODCS, Bitol ODPS) - providers/odcs/validation.py::collect_errors (fluid odcs validate) - providers/odps_standard/validation.py::validate - providers/opds/validator.py (pinned Draft202012Validator) - forge_datamodel/emit/osi_sidecar.py (the copilot's Ossie check) - extension_schemas.py::assemble_proposed_extensions (pinned Draft7Validator) So ODCS refused schema[].id "t 1" but took "t1\n", which an id: | block produces, and the copilot kept an LLM-proposed customScaffold block with use: "ci:basic\n", ref: "main\n" or id: "ci\n". All six now go through schema_validator, and say so with the same line-break hint. schema_validator takes a default dialect for a schema that declares none, passed to jsonschema's validator_for(default=), the way OpenStack Rally uses it. An extension schema is read in the dialect it declares, draft-07 when it declares none (the documented plugin contract), so a 2020-12 schema's unevaluatedProperties: false is enforced instead of ignored. OPDS keeps 2020-12 as its default. patternProperties keys keep re.search. jsonschema matches them in patternProperties, additionalProperties and unevaluatedProperties, and check-jsonschema's override of patternProperties alone lets a key that one keyword matches and another does not through additionalProperties: false unchecked. Only OPDS has key patterns (^[a-z]{2}$, three places). The tripwires now cover every shipped schema, pin that set, and also fail on a $recursiveRef or $dynamicRef, which re-pick the stock class as a nested $schema does. Follows #799.
- fluid odcs validate cuts each schema message to 120 characters, so for an id like customer_orders_v1 the hint ended "... block keeps and |", which reads as advice to use |. _brief cuts the message ahead of the hint instead; the hint is schema_manager.LINE_BREAK_HINT now, so the command can find it. - schema_validator raises ImportError when jsonschema could not be imported. validate_osi_document, the one unguarded caller, raised NameError: jsonschema instead. validate_opds reads the same JSONSCHEMA_AVAILABLE flag as the other checks; the find_spec probe that found an unimportable jsonschema is gone. - A test walks fluid_build/ and fails on any validator_for(), Draft*Validator() or jsonschema.validate() without cls= outside schema_manager, so a check added later cannot read $ as Python does again. It finds the six sites on the base and seven on the old main. - Tests for the flag branches of collect_errors and the artifact validators, and for OPDS reading an undeclared schema as 2020-12. - CHANGELOG: the extension check shows no message, so it carries no hint. - odps_standard.validate validates against the schema for the document's apiVersion, not only v1.0.0.
…s the run
Defect-scan follow-ups for the vendored-schema end-anchor change:
- fluid odcs validate printed each schema error as Rich markup, so a
failing value with "[/]" in it ended the command with "Unexpected
error: closing tag '[/]' ... has nothing to close" (exit 2, also on
main). The line is printed with markup=False, like the vowl line in
the same function.
- assemble_proposed_extensions raised when a plugin schema's $schema was
not a string: validator_for cannot look it up, where Draft7Validator
ignored it. A schema that cannot be applied now drops its block with a
redacted warning, as a block that fails its schema is dropped, and the
run goes on. This also covers {"type": 5}, which raised on main.
- The guard test also catches `from jsonschema import validate`,
`import jsonschema as js` with js.validate(...),
jsonschema.validators.validate/extend/create, and a cls= that does not
come from schema_validator. ODPS passes cls=type(schema_validator(...))
inline so the guard can see it. It parses only files that mention
jsonschema.
The rebase onto main put two new fluid diff --baseline entries between the FLUID-schema entry (#799) and this branch's, which begins "The fix above covered the FLUID schemas only".
📄 Documentation ReminderThis PR appears to be missing a documentation reference. Our docs live in a separate repo. Please update the PR description with one of:
See the Contributing Guide for details. |
…attern-trailing-newline # Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #799. That PR made FLUID contract, fragment and bundle validation read a schema
pattern's$as ECMA-262 does: the end of the value. Six other JSON Schema checks still ran jsonschema's ownpattern, which is Python'sre.search, and its$also matches before a final\n:forge/core/artifact_validators.py::_validate_against_schemafluid validate-artifacts(ODCS and Bitol ODPS files)providers/odcs/validation.py::collect_errorsfluid odcs validate, ODCS export and importproviders/odps_standard/validation.py::validateproviders/opds/validator.py::validate_against_opds_schemavalidate-artifacts(pinnedDraft202012Validator)forge_datamodel/emit/osi_sidecar.py::validate_osi_documentextension_schemas.py::assemble_proposed_extensionsextensions.<key>blocks (pinnedDraft7Validator)So ODCS refused
schema[].id: "t 1"but accepted"t1\n", which anid: |block produces. Against the real data-product-forge-custom-scaffold schema, the copilot also kept acustomScaffoldblock withuse: "ci:basic\n",ref: "main\n"orid: "ci\n". All six checks now go throughschema_manager.schema_validatorand carry #799's line-break hint.Decisions
schema_validator(schema, default=None)passesdefaultto jsonschema'svalidator_forfor a schema with no$schema. OpenStack Rally usesvalidator_for(..., default=Draft7Validator)the same way.Draft7Validator, the documented plugin contract. An undeclared plugin schema stays draft-07, sodependenciesstill works.unevaluatedProperties: falseis enforced instead of ignored.Draft202012Validator, which is what it pinned before.jsonschema.validate(the schema is checked first, then the best error is raised), now withcls=type(schema_validator(schema)).patternPropertieskeys keepre.search.patternProperties,additionalPropertiesandunevaluatedProperties. check-jsonschema overrides onlypatternProperties, which lets a key that one keyword matches and another does not throughadditionalProperties: falseunchecked. So I did not copy that approach.^[a-z]{2}$, in three places. Two of them accept any key.$defs/Languagesdoes not, so a hand-written OPDS file can carry{"en\n": ...}there. fluid's own emitter writesen.tests/test_schema_pattern_end_anchor.py) now cover all 13 shipped schemas, not only the FLUID ones.patternPropertiesset above.$recursiveRefand$dynamicRef. I checked that both re-pick jsonschema's stock class, the same as a nested$schemaor$ref: "#".fluid_build/and fails on any validator built outsideschema_manager. It catchesvalidator_for(),Draft*Validator(),jsonschema.validate()/js.validate()/jsonschema.validators.validate|extend|create()without acls=fromschema_validator, andfrom jsonschema import validate. It finds the six sites on the security: schema validation let a value with a trailing newline through a pattern #799 base and seven on the old main.schema_validatorraisesImportErrorwhen jsonschema could not be imported. Without that, the Ossie check, its one unguarded caller, raisedNameError: jsonschema.validate_opdsnow reads the sameJSONSCHEMA_AVAILABLEflag as the other checks. Thefind_specprobe, which found a jsonschema that then failed to import, is gone.Found while reviewing, and fixed here:
assemble_proposed_extensionsraised on a plugin schema whose$schemais not a string. Now a schema that cannot be applied drops its block with a redacted warning, and the run continues. That also covers{"type": 5}, which raised on main.fluid odcs validate(pre-existing).customer_orders_v1the hint ended... block keeps and |, which reads like advice to use|. The hint is now kept whole (schema_manager.LINE_BREAK_HINT).[/]ended the command withUnexpected error: closing tag '[/]' ... has nothing to close(exit 2, also on main). Now it prints withmarkup=False, and there is a CHANGELOG Fixed entry.Borrow receipts
schema_validator: reused. Its own receipts cover check-jsonschema, fastjsonschema/js-regex and networknt#1201.validator_for(schema, default=…): documented, and honoured for an unknown$schemasince 4.10.3. Prior art: OpenStack Rally 9f93c810.regex_variants.py: overridespattern,patternPropertiesandformat: regex, but notadditionalPropertiesorunevaluatedProperties. That gap is why I made the key decision above.Validation
tests/test_standard_schema_pattern_end_anchor.py:origin/main, and green here.[/], and OPDS's undeclared 2020-12. Mutations are caught: OPDSdefault=changed to Draft 7, and a disabledcollect_errorsguard.test_ci_*imports, live coding agent), or a timing flake that passes in isolation on the branch (machine load average was about 130).fluid generate artifacts examples/02-csv-to-data-product/contract.fluid.yaml, then the ODCSschema[0].idturned into anid: |block and the MANIFEST re-hashed.fluid validate-artifactsexits 1 here (at schema/0/id: 't1\n' does not match '^[A-Za-z0-9_-]+$' (it ends in a line break, …)) and 0 on main. Plain and|-ids exit 0 on both.fluid odcs validateon the same file exits 1 here and 0 on main.ruff check fluid_build/ tests/, the dead-code sweep,black==24.10.0 --check fluid_build/ tests/,scripts/check_license_headers.py, the product guardrail,detect-secrets-hook(1.5.0) and bothlint-importsconfigs.tests/perf/test_startup_budget.py::test_heavy_sdks_not_loaded_on_helppasses: jsonschema stays offfluid --help, and every new import is function-local. The module-count budgets read the same as on main locally.Reviews
/security-review: no findings on the main change. A focused re-review of the last fix commit is noted in a comment./world-class-inspection: architecture and tests PASS. Docs and code health raised P3 concerns, now fixed and re-verified: the ODPS docstring, the CHANGELOG hint wording, the OssieNameErrorand the dual availability probe. UX: the cut-off hint, fixed./defect-scan: four hunters (security, logic, runtime, cross-file contract) plus CLI journeys. Two real low defects came up, the malformed plugin$schemaand the[/]markup crash, and both are fixed above. Everything else was refuted or pre-existing.Follow-ups, pre-existing and out of scope
Draft7Validator, sofluid validate(via the plugin's extension validator) still acceptsuse: "ci:basic\n"orref: "main\n". It needs a fix in that repo.metadata.languagebecomes theproduct.detailskey. A code that does not match^[a-z]{2}$(EN) leaves that entry unvalidated, because the upstream schema allows any key there.fluid odcs validatecascade: a failingschema[]item also prints a cascadingUnevaluated properties are not allowed ('id', 'name', … were unexpected)error.fluid odcs validatemarkup: the parse-error, vowl and roundtrip lines in the same command still print exception text as markup.$as Python does. The first pass now catches these values.