Skip to content

fix(dnsbl): preserve TXT wire limits and bounded RRset cache lifetimes - #456

Open
seonghobae wants to merge 6 commits into
mainfrom
wardnet-hermes-product-20261002
Open

seonghobae wants to merge 6 commits into
mainfrom
wardnet-hermes-product-20261002

Conversation

@seonghobae

@seonghobae seonghobae commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Product defects and bounded repairs

Protected base: f8260f1e03836039ff9463dd99fa982e4e270c4b.
Current reviewed and published head: 1c855e93499d47f33496db4e94adbd8beb0ec8de.
Full 27-file candidate tree: 270487e7788d13caef7c1bbd57dbac263a540a36.
Complete base-to-head patch SHA256: 804a35647e1f43e15ce550fff998072a4366b492a40b6cf7cd2ecd76f722762e.

  1. Admitted DNSBL metadata was rendered as one oversized TXT string. A valid 600-byte reason plus source unit produced 612 decoded bytes and a real DNS parser rejected it. The Rust exporter now uses lossless adjacent strings at UTF-8 scalar boundaries, each at most 255 decoded bytes, retaining quote/backslash/control escaping.

  2. Admitted ttl_seconds was ignored by zone publication: a 1-second entry produced A/TXT records with the fixed $TTL 300. Valid lifetimes now appear explicitly except the byte-compatible 300-second default. DNSBL admission rejects values above 2147483647, and the untrusted persisted-state export boundary omits zero/out-of-range values rather than silently clamping them. Valid source entries sharing an IPv4 owner use the shortest published TTL consistently for both RRsets, without changing stored evidence or depending on input order.

  3. Origin filtering alone produced unloadable output for a 64-byte label, an interior empty label and a 256-byte fully qualified owner. Actual Rust output failed dnspython with LabelTooLong, EmptyLabel and NameTooLong, while 63-byte label and 255-byte owner controls passed. The sanitizer now retains normal spelling and existing filtering, but uses the existing dnsbl.invalid fallback for invalid labels or origins above 237 ASCII textual bytes. This reserves 16 encoded bytes for four maximal reversed IPv4 labels, satisfying 237 + 2 + 16 = 255 without changing stored entries, metadata or TTLs.

  4. Per-string chunking still admitted and exported an aggregate TXT RDATA of 65,536 octets: text parsing and raw RDATA serialization succeeded, but actual RR serialization returned FormError. The 65,535-octet positive control succeeds. Admission now counts decoded UTF-8 bytes plus every chunk length octet without allocating an escaped copy and rejects overflow. Persisted oversized entries are omitted from both A/TXT emission and shared-owner TTL selection, retaining stored evidence and otherwise-valid entries. Escaped master-file spelling does not count as wire payload.

Scope: DNSBL validation/publication and direct regression consumers only. No API shape/schema/authorization/feed-ownership change, foreign-owner dependency, deployment, central-workflow copy or competitor writer adoption. Existing MISP/shared DNSBL ownership repair remains with PR #167; official-feed/CIDR work remains with PR #115.

Actually executed RED → minimum repair → GREEN → independent review

  • TXT regression failed before production edits with exit101 and TXT character string exceeds 255 bytes: 612. Boundaries 254/255/256/510/511, long UTF-8, escapes, HTTP admission-to-export and short-output compatibility are retained.
  • TTL regression failed with [300,300] instead of [1,1]. Later negative controls separately demonstrated oversized admission/invalid persisted TTL and inconsistent shared-owner RRset TTLs; each failure receipt is preserved.
  • The independent review found an initial test packaging defect. Real extracted-core archive compilation reproduced it; package-local test support repaired it without exposing a production helper API.
  • Actual unchanged external scripts/smoke.sh failed at line212 because its 600-second fixture still expected a no-TTL record. Independent review returned CHANGES_REQUESTED. The minimal smoke correction requires literal600 on A and TXT while retaining the seed300 check. Real smoke then passed through restart/persistence. The negative review and RED receipts remain preserved.
  • The same substantive reviewer approved the corrected entire 14-file tree, not just a receipt audit. It retained independent source reasoning, core27 + HTTP2 executions, packaged-core27, real parser18 cases, deterministic mutation controls, and shell consumer positive/negative probes, attributed separately from the parent's full-suite executions. Local verdict SHA256: b7780764ef7e1901fb62d3c788750cb89c3179317311b579b7ab9d674269ccd6. This is not counted GitHub approval.

Subsequent whole-candidate test-sensitivity review

The next substantive reviewer approved the complete 17-file union, reusing the earlier source reasoning only for unchanged bytes and independently judging the six-path test/fuzz/docs delta and composition. Local verdict SHA256: 92e538ad3000171a4dbb2163bce411adceb5381ccd62850dc9623f607b6d5ccf. Its independently executed scratch test passed three positive and rejected fifteen negative controls. Parent executions and reviewer executions are attributed separately; no local verdict is counted GitHub approval. Production code, dependencies and public APIs are unchanged by the subsequent test-only commit.

Origin boundary whole-candidate source review

The subsequent read-only substantive reviewer approved the complete 20-file union and independently judged the seven-path origin repair and consumers; earlier reasoning was reused only for identical file bytes. Local verdict SHA256: 7885770b5a9665fc41e72e8edd8284d3fafa44f86cec736138e2412b8548c63e. It relied on official RFC content, not unsupported retrieval-ledger chronology. Parent executions inspected by the reviewer were not claimed as independent reruns. After the review, a new extracted core archive matched all eight changed core source/test files byte-for-byte and passed 36 tests, closing the previous final-fixture/archive mismatch. This is local technical approval, not counted GitHub approval.

Aggregate RDATA whole-candidate source review

The read-only substantive reviewer approved the complete 23-file union and directly inspected the twelve-path RDLENGTH increment, with prior reasoning reused only for identical file bytes. Local report SHA256: 8109e8dcb2c6f6669b50d58acc36a053e99a01bac231961d75d9d0cdda6ae72c. It reviewed parent executions separately and did not rerun tests/builds. Parent tests preserved actual admission, persisted-export and oracle REDs before the corresponding repairs; literal limits, UTF-8 overhead, quote/backslash/control bytes, near-boundary properties and real authenticated rejected-write readback/export are covered. The exact safely extracted final core archive passed 42 tests. No local verdict is counted GitHub approval.

User-directed self-hosted execution migration

The user requested organization-wide self-hosted transition and actual normalization on October 3, 2026. This owner changed only Wardnet's three retained local workflows and their existing Rust runner contract; central workflows and infrastructure remain with their existing owners. CI/Fuzz/Scorecard now require [self-hosted, Linux, X64, cwlab-ci-isolated]; CI/Fuzz no longer persist checkout credentials. Triggers, permissions, immutable action pins, concurrency, fuzz budgets and Scorecard v2.4.4 remain unchanged. There is no hosted or privileged-pool fallback.

The complete 27-file union received conditional local source approval, report SHA256 641fa5d9f35c117a41f2b0a699972c1200d2d66386b11254e9df0d3ac961a805. The reviewer independently read source and verified immutable bytes, but did not rerun native gates. Two actual new contract REDs preceded the routing/credential repair; all six retained runner/queue checks pass. Real actionlint passes with a scratch config declaring this exact custom label, without ignored findings. The label declaration is a routing requirement, not evidence of a provisioned runner.

Normalization is NOT complete: the current operator API inventory has zero matching isolated workers. All nine registered organization workers belong to restricted control/scanner/GPU pools; none is a safe general-CI substitute. Actual isolated provisioning, repository/workflow eligibility, clean per-job lifecycle, Node24/Rust/fuzz/Docker compatibility and exact-head successful jobs are still required. Arbitrary public-PR code must not reach persistent privileged/inference state; persist-credentials: false and an isolated-looking label alone are not a sandbox. No runner ACL, credential, billing, protection, inference service or production deployment was changed. The existing central owner/operator route has been notified; broadcast enqueue is not recipient adoption.

Exact committed-head local verification

Executed again on 1c855e93499d47f33496db4e94adbd8beb0ec8de, clean checkout:

  • cargo fmt --check — exit0.
  • cargo test --locked --offline --workspace — 219 passed, 0 failed/ignored/filtered, exit0.
  • cargo clippy --locked --offline --workspace --all-targets -- -D warnings — exit0.
  • git diff --check <protected-base> <head> — exit0.
  • Real actionlint on all three local workflows with the exact custom-label routing declaration — exit0; not runner provisioning.
  • bash scripts/smoke.sh — exit0 including real loopback HTTP, management auth, DNSBL A/TXT TTL, restart and persistence checks.
  • Separate scratch fuzz-wrapper cargo check --locked --offline ... --bin fuzz_dnsbl_zone — exit0; compilation only, not an executed fuzz campaign.

Parent real dnspython2.8.0 checks exercised exported TTL1/60/300/2147483647 through DNS parsing and message wire roundtrip, with three invalid persisted omissions. Extracted core archive27/27 and isolated synchronized fuzz-target compile passed. The latter is a scratch dependency compile diagnostic, not a canonical locked fuzz campaign.

The later extracted core archive passed 32 tests; its new helper bytes match the reviewed source, and the property file differs only by a descriptive comment. Stable Cargo gates and retained end-to-end smoke were rerun on the exact clean committed head.

Fresh-DB local Trivy vuln/misconfig CRITICAL/HIGH fixable scan of the exact 23-file candidate export returned exit0: Cargo.lock vulnerability0, Dockerfile20 success/0 failure and Kubernetes23 success/0 failure. This is the candidate tree, not a current remote merge-ref scan, hosted Security Scan or normal merge acceptance. Current-head CI/security/review/countable approval retain separate obligations; predecessor/Draft-exempt statuses do not transfer.

Honest remaining acceptance limits

The source-bound unexcluded final-candidate LLVM run reported 9842/10260 lines (95.9259259%), 827/907 functions and 13528/14195 regions. Both production instantiations of the new RDATA helper executed all25/25 regions. This report inventories11 production/unit-source files, not integration-test oracle source; it is a candidate execution, not a fresh committed-head coverage rerun. Full-workspace100% remains unmet; branches/MC-DC are unmeasured. The original100% requirement is not waived.

The earlier broad property-only mutation probes did not reject removal of chunking, explicit TTLs or RRset minimums; those negative observations remain preserved. The new independent package-local publication oracle checks input-derived A/TXT counts, owner/code identity, source order, shortest valid owner TTL and lossless decoded metadata. A dedicated property generates at least two publishable shared-owner records, while the original arbitrary-input path remains. In isolated archives, the exact new property executed one passing baseline test and one failing test for each of the three deliberate production mutations. An earlier replay selected zero tests and is explicitly excluded as invalid harness evidence. The fuzz target retains raw arbitrary inputs and adds a bounded positive projection; compilation does not prove a coverage-guided campaign ran.

Full DNS message fit beyond aggregate TXT RDLENGTH, general DNS name grammar beyond the emitted ASCII origin contract, CIDR/IPv6 publication, authoritative SOA/NS, threat/feed expiration, complete root archive publication, business/performance and rendered8locale acceptance are not established. No operational deployment, paid fallback, credential/profile edits, protection bypass or self-approval. Original dirty checkout13file hashes/status/HEAD remain preserved. This PR does not complete the repository-wide product goal.

Protocol grounding and evidence

  • Mockapetris, P. (1987). Domain names — implementation and specification (RFC1035), sections2.3.4/3.1/3.2.1/3.3/3.3.14/4.1.3/5.1. https://www.rfc-editor.org/rfc/rfc1035.txt
  • Elz, R., & Bush, R. (1997). Clarifications to the DNS specification (RFC2181), sections5.2/8. https://www.rfc-editor.org/rfc/rfc2181.txt
  • Source-current RCA: docs/doctoring/dnsbl-txt-character-strings.md and docs/doctoring/dnsbl-cache-lifetimes.md and docs/doctoring/dnsbl-origin-wire-limits.md and docs/doctoring/dnsbl-txt-rdata-limits.md.

No detection model/load-balancing algorithm or copyrighted-paper redistribution is introduced. Local real receipts, frozen manifests, raw coverage, preserved failures and independent reviews: /Users/seonghobae/orca/reports/hermes-rolling-migration/fleet/wardnet-hermes-product-evidence-20261002/.

Summary by CodeRabbit

  • 개선 사항
    • 긴 DNSBL 메타데이터를 TXT 레코드의 255바이트 제한에 맞춰 나누어 출력하며, UTF-8 문자를 보존합니다.
    • 따옴표, 백슬래시, 제어 문자 등을 안전하게 처리하고, 분할된 문자열을 합치면 원래 메타데이터가 유지됩니다.
  • 버그 수정
    • DNSBL TTL은 1초부터 2,147,483,647초까지 허용됩니다. 범위를 벗어난 저장 항목은 영역 출력에서 제외됩니다.
    • 같은 주소를 공유하는 항목에는 유효한 TTL 중 가장 짧은 값을 A·TXT 레코드에 적용합니다.
  • 문서
    • TXT 분할 규칙과 DNSBL 캐시 수명 처리 기준을 안내합니다.

@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f73d84fc-2f8d-4adb-a72b-58641b3382ee
📥 Commits

Reviewing files that changed from the base of the PR and between e1e4599 and 1c855e9.

📒 Files selected for processing (16)
  • .github/workflows/ci.yml
  • .github/workflows/fuzz.yml
  • .github/workflows/scorecard-analysis.yml
  • crates/waf-ids-core/src/lib.rs
  • crates/waf-ids-core/tests/dnsbl_rdlength.rs
  • crates/waf-ids-core/tests/dnsbl_zone_oracle.rs
  • crates/waf-ids-core/tests/fuzz_invariants.rs
  • crates/waf-ids-core/tests/support/dnsbl_txt.rs
  • crates/waf-ids-core/tests/support/dnsbl_zone.rs
  • docs/doctoring/dnsbl-txt-rdata-limits.md
  • docs/fuzzing.md
  • fuzz/fuzz_targets/fuzz_dnsbl_zone.rs
  • fuzz/support/dnsbl_zone.rs
  • tests/dnsbl_rdlength_export.rs
  • tests/support/dnsbl_txt.rs
  • tests/workflow_runner_contract.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/fuzzing.md

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

DNSBL 존 출력에 TTL과 TXT wire 크기 제한을 적용합니다. 같은 IPv4 owner에는 유효 TTL 중 최솟값을 사용합니다. 유효하지 않은 origin은 dnsbl.invalid로 대체합니다. 긴 TXT 메타데이터는 UTF-8 경계에서 최대 255바이트 문자열로 나눕니다. CI 워크플로의 러너 설정도 변경합니다.

Changes

DNSBL 존 게시

Layer / File(s) Summary
TTL 검증과 게시
crates/waf-ids-core/src/lib.rs, crates/waf-ids-core/tests/dnsbl_ttl.rs, tests/dnsbl_ttl_export.rs, docs/doctoring/dnsbl-cache-lifetimes.md, scripts/smoke.sh
TTL 상한을 2,147,483,647초로 설정하고 초과 값은 검증 단계에서 거부합니다. 존 출력은 TTL이 잘못된 저장 항목을 제외하고 같은 주소의 유효 TTL 중 최솟값을 A·TXT 레코드에 적용합니다. TTL 300초일 때 기존 출력 형식을 유지합니다.
TXT RDATA 제한과 분할
crates/waf-ids-core/src/lib.rs, crates/waf-ids-core/tests/dnsbl_rdlength.rs, crates/waf-ids-core/tests/dnsbl_txt_chunks.rs, crates/waf-ids-core/tests/support/*, tests/dnsbl_rdlength_export.rs, tests/dnsbl_txt_export.rs, tests/support/dnsbl_txt.rs, docs/doctoring/dnsbl-txt-*
TXT 메타데이터의 wire 크기를 65,535바이트 이하로 제한합니다. 출력 문자열은 UTF-8 문자 경계에서 최대 255바이트로 분할합니다. 독립 디코더와 회귀 테스트는 RDATA 유효성 및 메타데이터 복원을 확인합니다.
Origin 길이와 라벨 제한
crates/waf-ids-core/src/lib.rs, crates/waf-ids-core/tests/dnsbl_origin.rs, crates/waf-ids-core/tests/dnsbl_zone_oracle.rs, tests/dnsbl_origin_export.rs, docs/doctoring/dnsbl-origin-wire-limits.md
빈 라벨, 63바이트 초과 라벨 또는 IPv4 owner를 포함한 허용 길이 초과 origin에는 dnsbl.invalid를 사용합니다. 단위 테스트, HTTP 테스트와 오라클이 경계 사례를 검사합니다.
존 오라클과 생성 검증
crates/waf-ids-core/tests/fuzz_invariants.rs, crates/waf-ids-core/tests/support/dnsbl_zone.rs, crates/waf-ids-core/tests/dnsbl_zone_oracle.rs, fuzz/fuzz_targets/fuzz_dnsbl_zone.rs, fuzz/support/*, docs/fuzzing.md
독립 오라클, 속성 테스트와 퍼즈 타깃이 레코드, TTL, origin, owner 및 TXT 데이터를 입력 항목과 대조합니다.

CI 러너 계약

Layer / File(s) Summary
워크플로 러너 설정과 계약
.github/workflows/ci.yml, .github/workflows/fuzz.yml, .github/workflows/scorecard-analysis.yml, tests/workflow_runner_contract.rs
세 워크플로의 러너를 지정된 격리형 self-hosted 러너로 변경하고 checkout 자격 증명 저장을 비활성화합니다. 계약 테스트는 워크플로의 러너 레이블과 checkout 설정을 검사합니다.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Merge Risk: 🔵 Low · up to 1c855

The DNSBL TTL, TXT and origin changes look well covered by tests. Before merging, confirm that a runner with the cwlab-ci-isolated label is registered and available to this repository, otherwise the CI jobs will wait and not run.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 1c855

The DNS publishing repairs improve wire-format and cache-lifetime correctness. However, running pull-request builds on self-hosted machines depends on isolation and cleanup guarantees that are not demonstrated here. Upgrades can also stop publishing older entries with newly disallowed lifetimes, requiring a compatibility check for existing deployments.

Retained concerns

  • Medium · security · inferred: PR-controlled builds now execute on an operator-managed pool also selected by Scorecard jobs with security-events write permission. The reviewed configuration establishes routing, not disposable hosts or cleanup. If hosts or executable state survive between trust levels, a PR build could influence a later trusted job. Actual reuse, ambient credentials and network reachability are unverified, so this is a conditional architecture concern, not a verified compromise.
  • Medium · reliability · observed: An otherwise valid IPv4 entry with ttl_seconds above 2147483647 could be admitted by the base and published with effective TTL 300. Head excludes that entry from both A and TXT publication while leaving it in stored state. This intentionally tightens the contract, but upgrades can remove previously published blocklist answers. Whether affected entries exist and whether downstream consumers treat their absence as permission remain unresolved.
Security review details

Security Blast Radius

  • inferred — The runner change exposes workers assigned from the labeled pool to repository build code on eligible PR runs. The pool is also selected for scheduled fuzzing and Scorecard. Exposure to other services, credentials or environments cannot be established from labels; it depends on external worker provisioning and network policy.
  • inferred — The legacy-TTL compatibility concern is limited to owners whose previously published entries become excluded. Its security consequence depends on how DNSBL consumers interpret missing answers; it does not establish a change to gateway enforcement or threat-feed expiration.

Security Findings and Attack Paths

  • inferred — A contributor whose PR job is permitted to run can supply code executed by cargo builds or fuzz targets. If worker-local executable state survives into a later trusted job, that state could influence a job carrying greater authority, including Scorecard's security-events write token. Persistence across jobs is not demonstrated, and no credential theft or compromise is verified.

Trust Boundaries and Controls

  • observed — CI and fuzz grant contents read permission, and all three workflows disable checkout credential persistence. Actions remain pinned. These constrain workflow-token and checkout exposure, but do not enforce worker exclusivity, ambient-credential removal, network isolation or teardown.
  • observed — DNSBL export treats persisted entries as untrusted publication input. Only IPv4 owners with canonical 127/8 answers, valid lifetimes and bounded metadata contribute to records or owner TTLs. Origin filtering and quoted metadata escaping constrain attacker-controlled bytes before zone output.

Resilience and Maintainability Implications

  • observed — The runner tests detect drift in the listed workflow set, literal runner assignments and checkout credential settings. They do not validate provisioning or cleanup after successful, failed or cancelled jobs. CI and fuzz can cancel superseded PR runs, making interruption cleanup part of the external runner lifecycle contract.

Hardening Proposals

  • proposed — Before enabling PR execution on this pool, establish disposable per-job workers or an independently enforced reset boundary, including cleanup after failure and cancellation. Keep PR workers free of ambient privileged credentials and control-plane access, and isolate their reusable executable state from write-capable trusted jobs.
  • proposed — Before upgrading a state-backed deployment, identify entries newly excluded by the publication policy and compare published owner sets. Explicitly reconcile legitimate legacy entries to approved lifetimes rather than assuming omission preserves enforcement, retaining the original state for rollback assessment.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 89.66% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 58 functions across 19 files. (5 skipped: 5…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 TXT wire 한도와 RRset TTL 제한이라는 주요 변경 사항을 명확하고 간결하게 설명합니다.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae
seonghobae marked this pull request as ready for review October 2, 2026 12:32

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@seonghobae seonghobae changed the title fix(dnsbl): publish long TXT metadata as wire-sized character strings fix(dnsbl): preserve TXT wire limits and bounded RRset cache lifetimes Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants