{"uuid": "c0ddd691-6157-4bbd-a438-7fc4c29f4b42", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-66066", "type": "seen", "source": "https://gist.github.com/jakubkuzma-sm/1cce639485f3310873c25dbe5b9b473c", "content": "\n\n\n\n\nProSaWeb: September 2026 Review\n\n\n\n@font-face {\nfont-family: 'Make It Better';\nsrc: url('https://www.scout24.com/assets/Fonts/MakeItBetter-Regular.woff2') format('woff2');\nfont-weight: 400;\nfont-style: normal;\nfont-display: swap;\n}\n@font-face {\nfont-family: 'Make It Better';\nsrc: url('https://www.scout24.com/assets/Fonts/MakeItBetter-Bold.woff2') format('woff2');\nfont-weight: 700;\nfont-style: normal;\nfont-display: swap;\n}\n:root {\n--s24-orange: #FF8A00;\n--s24-orange-dark: #E67300;\n--s24-ink: #1B1B1B;\n--s24-cream: #FFF5EB;\n--r-background-color: #ffffff;\n--r-main-font: 'Make It Better', Arial, sans-serif;\n}\n.reveal { font-family: 'Make It Better', Arial, sans-serif; color: var(--s24-ink); }\n.reveal h1 { font-size: 2.4em; color: var(--s24-ink); font-weight: 700; letter-spacing: -0.02em; }\n.reveal h1::after { content: ''; display: block; width: 60px; height: 5px; background: var(--s24-orange); border-radius: 3px; margin: 0.3em auto 0; }\n.reveal h2 { font-size: 1.6em; color: var(--s24-ink); font-weight: 700; border-bottom: 3px solid var(--s24-orange); padding-bottom: 0.2em; }\n.reveal h3 { font-size: 1.1em; color: var(--s24-orange-dark); font-weight: 700; }\n.reveal table { font-size: 0.65em; margin: 0.5em auto; border-collapse: collapse; }\n.reveal th { background: var(--s24-orange); color: white; padding: 0.4em 0.8em; text-align: left; font-weight: 700; }\n.reveal td { padding: 0.35em 0.8em; border-bottom: 1px solid #F0E0D0; }\n.reveal tr:nth-child(even) { background: var(--s24-cream); }\n.reveal .subtitle { color: #666; font-size: 0.7em; margin-top: -0.5em; }\n.reveal .stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.8em; margin-top: 0.5em; }\n.reveal .stat-box { background: var(--s24-orange); color: white; border-radius: 12px; padding: 0.8em; text-align: center; }\n.reveal .stat-box .number { font-size: 2em; font-weight: 700; }\n.reveal .stat-box .label { font-size: 0.7em; opacity: 0.95; }\n.reveal .version-tag { display: inline-block; padding: 0.15em 0.5em; border-radius: 4px; font-size: 0.6em; font-weight: 700; }\n.reveal .vtag-patch { background: #E8E8E8; color: #555; }\n.reveal .vtag-minor { background: var(--s24-orange); color: white; }\n.reveal .vtag-major { background: var(--s24-ink); color: white; }\n.reveal .ticket { font-family: 'SF Mono', 'Fira Code', 'Courier New', monospace; font-size: 0.85em; color: var(--s24-orange-dark); font-weight: 700; }\n.reveal ul { font-size: 0.75em; text-align: left; }\n.reveal li { margin-bottom: 0.3em; }\n.reveal .small { font-size: 0.6em; color: #777; }\n.reveal strong { color: var(--s24-ink); font-weight: 700; }\n.reveal section.present { padding: 0 0.5em; }\n\n\n\n\n\n\n\n\n\n\n\n\nProSaWeb\n\nSeptember 2026 Review\n\nDaniel &amp; Jakub\n\n\n\n\n\n\nMonth at a Glance\n\n\n\n\n\n2\n\nProduction Releases\n\n\n\n\n28\n\nFeature/Fix PRs\n\n\n\n\n78\n\nPRs Merged\n\n\n\n\n8\n\nSecurity Fixes\n\n\n\nv2.37.0 (Sep 18), v2.37.1 (Sep 21). Feature/fix PRs: +5,576 / -287 across 202 files.\n\n\n\n\n\n\nJira to Linear Migration\n\nSep 7\n\n\n\nAll ticket tracking moved from Jira (PN project) to Linear, team SPN2 (SPN-ProSaWeb)\n\nOpen PN tickets migrated with numbering preserved: PN-2520 \u2192 SPN2-2520 (label Migrated)\n\nCommit and PR prefixes switched [PN-\u2026] \u2192 [SPN2-\u2026] on Sep 7\n\nJira stays read-only for history, new work lands in Linear\n\n\n\n\n\n\n\nReleases\n\n\nVersionDateHighlights\n\nv2.37.0Sep 18Security hardening, API timeouts, production bug fixes, tenant admin filters\nv2.37.1Sep 21Hotfix: MFH income-factor PATCH with parking/garage (SPN2-2548)\n\n\n\n\npatch bugfix |\nminor features |\nmajor breaking\n\n\nWork merged after v2.37.1 is queued for v2.38.0.\n\n\n\n\n\n\n\n\nSecurity &amp; Authorization Hardening\n\nCross-tenant access audit\n\n\n\n\nCross-Tenant Access Closed (1/2)\n\n\nTicketDescription\n\nSPN2-2506BuildingsController cross-tenant authorization\nSPN2-2520ReportContext valuation fallback via foreign valuation_id\nSPN2-2521GarageUnits + RemainingUsefulLives scoped through authorized report (2 security-review rounds)\nSPN2-2522TemplateVariablesController: bare finders replaced, authorization added\n\n\n\n\n\n\nCross-Tenant Access Closed (2/2)\n\n\nTicketDescription\n\nSPN2-2523CustomCorrectionFactors authorized against own report\nSPN2-2524NCC config controllers: authorize create/update/destroy\nSPN2-2525Legacy report redirects moved behind authentication\nSPN2-2538CommercialUnits mass-assignable building_id (reparenting), closed via review\n\n\n\nAlso: VCR cassette credentials scrubbed. Shop API fails closed when credentials are missing (SPN2-2305).\n\n\n\n\n\n\n\nImage Processing CVE\n\nSPN2-2532: CVE-2026-66066 (CVSS 9.5), file read + RCE via Active Storage/libvips\n\n\n\nExposure window: vulnerable Rails 8.1.3 in prod from v2.34.0 (Jul 6) to v2.35.1 (Jul 30)\n\nPatched in ~1 day (8.1.3.1 on Jul 29), roughly 4 weeks before mass exploitation began\n\nForensic scan of the production read replica + S3 (Rails official tooling) over the full window: 28 blobs examined, 0 crafted, 0 read errors\n\nBreach check: no crafted MAT file ever in Active Storage during the window, and evidence could not have been purged (no purge hooks on cover_photo)\n\nVerdict: not exploited, no secret rotation required\n\n\n\n\n\n\n\nProduction Bug Fixes\n\n\nTicketDescription\n\nSPN2-2530Garage plot delegation crash when independent_building is nil\nSPN2-2534DOCX generation crash on missing cover photo/logo\nSPN2-2539NoSuchKey XML error on document download\nSPN2-2541CustomCorrectionFactors edit/update 500: @report never set\nSPN2-2544CustomCorrectionFactors edit/new views: wrong route helper\nSPN2-2537Unsaved-changes warning on read-only document preview\nSPN2-2549500 when following the BGF warning link\n\n\n\n\n\n\n\n\nAPI Resilience &amp; Correctness\n\n\nTicketDescription\n\nSPN2-2542Geocoder timeouts retried, 502 instead of 500, no response-body leak\nSPN2-2543Request timeouts on Api::Client shared HTTP clients\nSPN2-2528Reject living_space below accommodation_units floor at the API boundary\nSPN2-2548MFH income-factor PATCH with parking/garage, 422 instead of 500 (v2.37.1)\nSPN2-2547Handled validation errors no longer leak to Datadog Error Tracking\n\n\n\n\n\n\n\n\nValuation &amp; Calculation Correctness\n\n\nTicketDescription\n\nSPN2-2337Comparable selection persisted in Zus\u00e4tzliche Markanpassung (stable identity, dedicated column)\nSPN2-2266Rent development limited to the valuation cut-off date (in progress)\nSPN2-2461NDGA: EFH unit named \"Wohnung\" instead of \"Hauptwohnung\"\nSPN2-2288Prevent Eigentumswohnung in Volleigentum evaluations\n\n\n\n\n\n\n\n\nUI / UX Improvements\n\n\nTicketDescription\n\nSPN2-2456Warn on unsaved changes after in-page add, keep warning on record deletion (Done)\nSPN2-2526Options menu button for garage unit rows\nSPN2-2527Discard changes keeps you on the current building section\n\n\n\n\n\n\n\n\nNDGA Document Templates\n\nImportable Prosadocx fragments (gists)\n\n\nTicketDescription\n\nSPN2-2533NDGA fragment import: FG M\u00fcnchen ruling 26.06.2026, 8 K 1147/23\nSPN2-2536Missing two lines in table ND_4: fix for ND_4.1 Allgemeines (Wohnen)\n\n\n\n\n\n\n\n\nAPI Initiative\n\n\nTicketDescriptionStatus\n\nSPN2-2546User APIs OpenAPI spec for RND (GoValue)Test Approval\nSPN2-2545Provisioning and Entitlement for ValuationsIn Progress\nSPN2-2510Setup the api. subdomainIn Progress\nSPN2-2511OAuth 2.0 flowIn Progress\nSPN2-2515Setup WiZZ with LamiTest Approval\n\n\n\n\n\n\n\n\nWins\n\n\n\nSecurity: 8 cross-tenant authorization holes closed, 2 full security-review rounds on SPN2-2521\n\nAPI: timeout policy and correct status codes (422/502) for consumers\n\nReleases: v2.37.0 mid-month, hotfix v2.37.1 three days later\n\nDocuments: DOCX crashes on missing assets and NoSuchKey downloads eliminated\n\nProcess: Jira to Linear migration done in a day, numbering preserved, no work lost\n\n\n\n\n\n\n\nReveal.initialize({\nhash: true,\nslideNumber: true,\ncontrols: true,\nprogress: true,\ncenter: true,\ntransition: 'slide',\nwidth: 1200,\nheight: 700\n});\n\n\n\n\n\n# ProSaWeb: September 2026 Summary\n\n*Generated 2026-09-30 from git/GitHub (`gh`), Linear (`lin`), and Jira (`acli`).*\n\n## Releases\n\n| Version | Date | Highlights |\n|---------|------|------------|\n| **v2.37.0** | Sep 18 | Security hardening wave (8 authorization fixes), API resilience (geocoder + Api::Client timeouts), production bug fixes (DOCX, document download, garage plot), tenant admin filters |\n| **v2.37.1** | Sep 21 | Hotfix: allow MFH income-factor PATCH when the MFH has parking/garage (SPN2-2548) |\n\n## By the numbers\n\n- **78 PRs merged**: 28 feature/fix (25 Kuba, 3 Daniel) + 50 dependabot\n- **86 commits** to `main`; feature/fix PRs: +5,576 / -287 across 202 files\n- **2 releases** shipped (both by Daniel)\n- **2 NDGA template fixes** delivered as importable gists\n- **Tracker migration**: Jira (`PN-*`) to Linear (`SPN2-*`) on Sep 7\n\n## Tracker migration (Sep 7)\n\nTicket tracking moved from **Jira (PN project)** to **Linear (SPN2, SPN-ProSaWeb)**. All open PN tickets were migrated (Linear label `Migrated`); commit/PR prefixes switched from `[PN-\u2026]` to `[SPN2-\u2026]`. Jira remains read-only for historical tickets; all new work lands in Linear.\n\n## Theme 1: Security and authorization hardening\n\nA cross-tenant authorization audit: 8 controllers/sub-resources closed off, several through two rounds of security review.\n\n| Ticket | PR | Work |\n|--------|----|------|\n| SPN2-2506 | #1506 | BuildingsController cross-tenant authorization |\n| SPN2-2520 | #1526 | ReportContext valuation fallback: cross-tenant access via foreign valuation_id |\n| SPN2-2521 | #1545 | GarageUnits + RemainingUsefulLives scoped through authorized report (2 security-review rounds, regression coverage) |\n| SPN2-2522 | #1561 | TemplateVariablesController: bare finders replaced, authorization added |\n| SPN2-2523 | #1556 | CustomCorrectionFactors authorized against own report |\n| SPN2-2524 | #1530 | NCC config controllers: authorize create/update/destroy |\n| SPN2-2525 | #1540 | Legacy report redirects moved behind authentication |\n| SPN2-2538 | | CommercialUnits mass-assignable `building_id` (cross-tenant reparenting), closed via SPN2-2521 review |\n\nAlso: credentials scrubbed from VCR cassettes; Shop API fails closed when credentials are not configured (SPN2-2305).\n\n## Security review: Image Processing CVE (SPN2-2532)\n\nCVE-2026-66066 (\"KindaRails2Shell\", CVSS 9.5): arbitrary file read + RCE via Active Storage variant processing with libvips. Raised Sep 9, investigated the same week.\n\n- **Exposure window:** vulnerable Rails 8.1.3 ran in prod from v2.34.0 (Jul 6) to v2.35.1 (Jul 30). Patched to 8.1.3.1 on Jul 29 (commit `a0d529eee`), ~1 day after disclosure and ~4 weeks before mass exploitation began (~Aug 24).\n- **Forensic scan** (Rails official tooling, production read replica + S3, full window Jul 6 to Jul 30): Scan A (rendered attack evidence): 0 candidates, 0 failed. Scan B (--all-candidates): 28 examined, all clean, 0 crafted. Full S3 visibility, 0 read errors.\n- **Breach check:** no crafted MAT file ever existed in Active Storage during the window. Exploit conditions were all in place (variants via `:vips`, direct uploads reachable), but no evidence of use.\n- **Cleanup-after-exploit analysis:** evidence could not have been purged by an attacker (`has_one_attached :cover_photo` has no `dependent: :purge_later`, no purge hooks; ReportDocument deletion removes only the attachment row).\n- **Verdict:** not exploited. No secret rotation required (rotation would log out all users and invalidate signed cookies/Active Storage URLs without reducing real risk).\n\nFollow-up noted: `config/storage.yml` does not map `AWS_SESSION_TOKEN`, which broke read-only local S3 tooling (worked around via temporary AWS profile). Hardening ticket pending, unrelated to this CVE.\n\n## Theme 2: Production bug fixes\n\n| Ticket | PR | Work |\n|--------|----|------|\n| SPN2-2530 | #1537 | Garage plot delegation crash when `independent_building` is nil |\n| SPN2-2534 | #1538 | DOCX generation crash on missing cover photo/logo |\n| SPN2-2537 | #1544 | Unsaved-changes warning removed from read-only document preview |\n| SPN2-2539 | #1549 | NoSuchKey XML error on document download; DOCX regeneration no longer enqueued from the download GET |\n| SPN2-2541 | #1562 | CustomCorrectionFactorsController#edit/#update 500 (`@report`/`@building` never set) |\n| SPN2-2544 | #1563 | CustomCorrectionFactors edit/new views: wrong route helper |\n| SPN2-2549 | #1579 | 500 when following the BGF warning link |\n\n## Theme 3: API resilience and correctness\n\n| Ticket | PR | Work |\n|--------|----|------|\n| SPN2-2542 | #1558 | Geocoder gateway timeouts retried, 502 instead of 500, response body kept out of API errors |\n| SPN2-2543 | #1559 | Request timeouts on `Api::Client` (shared HTTP clients) |\n| SPN2-2528 | #1525 | Reject `living_space` below the accommodation_units floor at the API boundary |\n| SPN2-2548 | #1568 | MFH income-factor PATCH with parking/garage allowed; 422 instead of 500 when no residential unit (v2.37.1) |\n| SPN2-2547 | | Handled API validation errors no longer leak to Datadog Error Tracking |\n\n## Theme 4: Valuation and calculation correctness\n\n| Ticket | PR | Work |\n|--------|----|------|\n| SPN2-2337 | #1564 | Persist comparable selection in Zus\u00e4tzliche Markanpassung (stable identity, dedicated column, hardened save path) |\n| SPN2-2266 | in progress | Rent development limited to the valuation cut-off date |\n| SPN2-2461 | #1573 | NDGA: EFH accommodation unit named \"Wohnung\" instead of \"Hauptwohnung\" |\n| SPN2-2288 | #1578 | Prevent Eigentumswohnung creation in Volleigentum evaluations |\n\n## Theme 5: UX and unsaved-changes behavior\n\n| Ticket | PR | Work |\n|--------|----|------|\n| SPN2-2456 | #1507, #1519 | Warn on unsaved changes after in-page add; keep warning when a record is marked for deletion (Done) |\n| SPN2-2526 | #1520 | Options menu button for garage unit rows |\n| SPN2-2527 | #1574 | Discard changes keeps you on the current building section |\n\n## Theme 6: NDGA document templates (importable gists)\n\n| Ticket | Work |\n|--------|------|\n| SPN2-2533 | NDGA fragment import: FG M\u00fcnchen ruling 26.06.2026, 8 K 1147/23 |\n| SPN2-2536 | NDGA missing two lines in table ND_4: importable fix for ND_4.1 Allgemeines (Wohnen) |\n\n## API initiative (Linear, in flight)\n\n| Ticket | Status | Work |\n|--------|--------|------|\n| SPN2-2546 | Test Approval | User APIs OpenAPI spec for RND (GoValue) |\n| SPN2-2545 | In Progress | Provisioning and Entitlement for Valuations |\n| SPN2-2510 | In Progress | Setup the `api.` subdomain |\n| SPN2-2511 | In Progress | OAuth 2.0 flow |\n| SPN2-2515 | Test Approval | Setup WiZZ with Lami |\n| SPN2-2305 | Test Approval | Tenant admin list: status/origin/activation filters |\n\n## Notes\n\n- v2.37.0 (Sep 18) absorbed the security wave; v2.37.1 shipped 3 days later as a targeted hotfix.\n- SPN2-2531 (\"NHK Calculation is wrong\") was investigated and canceled as invalid.\n- Work merged after v2.37.1 (SPN2-2337, SPN2-2288, SPN2-2461, SPN2-2549) is queued for v2.38.0.\n", "creation_timestamp": "2026-09-25T10:28:11.000000Z"}