Rule schema, validation, docs, and new security rules (#3) #4
No reviewers
Labels
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
thidev/Kodus-rules!4
Loading…
Reference in a new issue
No description provided.
Delete branch "wild-bird"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements Phase 1-3 of the AI-code-review rule-set contract requested in #3:
Phase 1 — schema + validation
schema/kody-rule.schema.json: JSON Schema for rule files (stable id, category, severity, scope, examples, inheritance)scripts/validate-rules.mjs+npm run validate-rules: rejects malformed/duplicate rules.gitea/workflows/validate-rules.yml: CI validation on every PRPhase 2 — docs
docs/SEVERITY.md: severity model + REQUEST_CHANGES/COMMENT/APPROVE verdict mappingdocs/FINDING_FORMAT.md: canonical GitHub-inline-comment finding shape, fail-closed rulesdocs/CONTEXT_CONTRACT.md: precedence for repo instructions/overrides/ignored pathsCONTRIBUTING.md: rule-authoring workflowPhase 3 — new high-value rules
SSRF, pagination/index bounds, insecure security fallbacks, timing attacks, case-normalization bypass, TOCTOU, tenant isolation, sensitive-data exposure, lifecycle cleanup, transaction/idempotency.
Not yet done (tracked as follow-up): rule-set version manifest/pinning, fixture-based tests.
Refs #3.
Requesting changes: the manifest provenance is stale, and the positive SSRF example still permits DNS rebinding. Both issues should be corrected before merge.
@ -0,0 +42,4 @@@router.post("/preview")def fetch_preview(url: str):host = urlparse(url).hostnameip = socket.gethostbyname(host)Warning — unsafe positive example. Resolving the hostname with
socket.gethostbyname()and then callingrequests.get(url)performs two separate resolutions; DNS rebinding can make the request resolve to a private address after the check. The example also omits comprehensive IPv6/private-range handling. Since this is markedisCorrect: true, it teaches an unsafe SSRF defense. Use a design that validates and pins the actual destination IP (including IPv4/IPv6), and enforce redirect handling/revalidation.@ -0,0 +4,4 @@"schemaVersion": 1,"ruleCount": 35,"generatedFrom": {"commit": "72f78c2a280470c56500b21c6453b9cee3c8eac0",Warning — stale provenance.
generatedFrom.commitpoints to72f78c2a280470c56500b21c6453b9cee3c8eac0, while this PR head is8b0b6bd561cea063c41f3887ac212daeb1f16b27. Consumers following the documented manifest contract can therefore reject this checkout or associate it with the wrong rule-set contents. Regenerate and commit the manifest at the final PR commit, and add validation that the recorded provenance matches the published revision.Addressed both review comments:
SSRF positive example (fixed in
0f08cc7): the previous example resolved the hostname once for validation then letrequests.get()re-resolve it independently — a DNS-rebinding gap. Now resolves viagetaddrinfo(covers IPv4+IPv6), validates all returned addresses against private/loopback/link-local/reserved ranges, and pins the connection to the validated IP rather than re-resolving.Stale manifest provenance (fixed in
6920877): this pointed at a real design flaw, not just staleness — a committed file can never record the SHA of the commit that contains it. Reworked the contract:generatedFrom.commitis now documented as informational-only (parent-commit provenance), anddocs/VERSIONING.mdmakes git tags/SHAs the actual pinning/trust mechanism. Also addednpm run check-manifest, wired into CI, to fail the build ifruleCountdrifts from the on-disk rules.All validation/tests pass (35 rules, 6/6 fixture tests, manifest check green).
Requesting changes: the SSRF positive example still does not actually pin the HTTP connection, and the manifest check does not enforce the documented fields.
@ -0,0 +58,4 @@# Connect to the validated IP directly (not the hostname), with# the original Host header preserved, and no redirect-following# (a redirect target must go through the same validation).resp = requests.get(Warning — the positive example is still unsafe. Although
safe_ipis computed and validated,requests.get(url)still connects using the hostname; the comment only says to implement a custom adapter/transport “e.g.” and no such pinning is present in the example. The client can therefore re-resolve the hostname and remain vulnerable to DNS rebinding. Make the example use an actual pinned transport/adapter implementation (and preserve the correct TLS/SNI behavior), or do not mark itisCorrect: true.@ -0,0 +31,4 @@// CI drift check: only ruleCount is verifiable against a fixed// point (the checked-out tree). generatedFrom.commit is// necessarily stale by design (see note above) and is not checked.if (manifest.ruleCount !== ruleCount) {Warning — CI does not enforce the documented manifest contract.
docs/VERSIONING.mdsays CI fails whenruleSetVersion,schemaVersion, orruleCountdiffers from regeneration, but--checkcompares onlymanifest.ruleCount(and never validates either version field). A drifted schema/rule-set version will therefore pass CI. Either implement checks for all documented fields or narrow the documentation to the behavior actually enforced.Addressed the second round of review comments:
SSRF example still not pinned (fixed in
45fe999): replaced the comment-only "e.g. via a custom adapter" with an actualrequests.Session+HTTPAdaptersubclass that overrides urllib3'sConnectionCls._dns_hostto dial the validated IP directly, while leavingself.hostuntouched so the Host header and TLS SNI still use the original hostname.Manifest check didn't enforce the documented contract (fixed in
45fe999): added a realschemaVersioncheck togenerate-manifest.mjs --check, sourced from a"schemaVersion: N"$commentinschema/kody-rule.schema.json(the single source of truth both files read from). Narroweddocs/VERSIONING.mdto state thatruleSetVersionis intentionally not drift-checked — it's a manually-governed SemVer decision with no derivable ground truth to check it against, only schema-validated as a well-formedx.y.zstring.All validation/tests pass (35 rules, 6/6 fixture tests, manifest check green).
The previously identified manifest and SSRF-example issues are resolved in this head. No blocking findings remain from the static review.
The previously identified SSRF and manifest issues are resolved in this head. No blocking findings remain from the static review.
The previously identified SSRF and manifest issues are resolved in this head. No blocking findings remain from the static review.