Eden
All case studies
CVE-2026-126082

Rails Active Storage ImageMagick coder abuse (CVE-2026-126082)

The historical Active Storage advisory described arbitrary file writes when an affected Rails application processes attachments through ImageMagick, with possible code execution. The CVE record is now rejected. Check the referenced advisory and release notes to determine which attachment-processing fix applies to the installed branch.

Public CVE record · Vendor or publisher advisory · Source and API bundle

FieldValue
Affected component and versionsRuby on Rails · 8.1.0.beta1 before 8.1.4.1, before 8.0.5.2
Fixed release8.1.4.1, 8.0.5.2
Evidence statusHistorical fixture record, rejected CVE identifier
Test environmentLab fixture or affected build documented in the evidence below
Eden release and module version1.103.0 · module 1.0.0
Tested at2026-11-11 UTC
First publicly published2026-11-12T02:29:06Z

cve.now entry.

Eden terminates the HTTP connection to the affected product, evaluates a declarative policy and — in tier 2 — a separately compiled native Rust module under signed module authority, before any byte reaches the application. It does not patch the affected component; the vendor's fixed release remains the durable repair.

Status: live two-tier remediation recorded (blocklist; partiality stated) 2026-11-11 on the installed eden-product-release:1.103.0-single lab container (image sha256:809293e1f0b525ac04698d1fa8d7ffdc66cbee1ff690b082ba26b4d050f08416).

The vulnerability

Processing uploads with ImageMagick under a policy that does not deny the involved coders lets an unauthenticated attacker write/read arbitrary files (fixed in 8.0.5.2 / 8.1.4.1).

The Eden patch

Tier 1 — declarative policy (blunt, fail-closed): deny on the affected route family — takes the whole surface offline, legitimate calls included.

Tier 2 — native module: gabriel_railsas_v1, binary SHA-256 eec96dffd602b48f6bcbfd0147d3044326e84f768daf4abdfe38f0000a1930e7 (source 9c2d94a522965d249066ef4e14dc6776686827d53f2d2b9ce4f19032eb0413bb) — refuse dangerous-coder content markers (MVG command sequences, script-bearing SVG) in upload bodies.

Implement the remediation

The CVE identifier was rejected. Use the Rails security advisory for Active Storage image analysis and verify ordinary attachment analysis on the upgraded branch.

  1. Identify the affected installation. Check the installed component and the advisory’s prerequisites.
  2. Apply the vendor fix. Use the advisory to select the fixed release for the installed branch. Reported fixed releases include 8.1.4.1, 8.0.5.2. Follow the vendor’s update instructions and confirm the version actually loaded by the service.
  3. Verify the upgrade. Confirm the running version and repeat normal operations on the upgraded service. Use an isolated environment for security regression probes.
  4. Restore service if the update fails. Follow the vendor’s recovery instructions and keep the affected operation isolated until the supported fix is active.

Measured comparison (live, 2026-11-11)

ProbeBaseline (unprotected)Tier 1Tier 2 (module)
benign_image_upload200403200
attack_mvg_coder200 (coder_abused ×1, file_written ×1)403403
attack_svg_script200 (coder_abused ×1, file_written ×1)403403

Performance trace (Eden telemetry, live 2026-11-11)

Measured with Eden's built-in request telemetry (gateway.request.baseline spans, read back through /api/v1/analytics/telemetry?signal=traces), not client wall-clock. Latency Eden introduces per remediation arm for this case:

Armnmeanp50p95min–max
Tier 1 — declarative policy, blocked request (403)1527 µs23 µs49 µs14 µs–49 µs
Tier 2 — native module, benign passes through (200)23317 µs291 µs543 µs263 µs–588 µs
Tier 2 — native module, attack refused (403)14150 µs133 µs265 µs102 µs–265 µs

Eden-added latency for the tier-2 remediation: benign requests complete in 317 µs mean (p50 291 µs, p95 543 µs) in-product, module evaluation included. Honest scope: lab fixtures on one host, fresh connection per measured request, warm requests only; module reactivated shards 2/2 with a fresh signed statement per measurement; spans carry the full authority lineage (module SHA-256, operation, publication). Not the EDEN-3640 performance qualification contract.

Tested against: eden-product-release:1.103.0-single (image sha256:809293e1f0b525ac…, build target eden_service_single, tooling revision 81a0724a…). Raw data: module2/telemetry-ab/railsas-telemetry.json and module2/tier1-ab/railsas-tier1.json.

Authority lifecycle and negatives

Driven through the full signed lifecycle (module2/evidence/railsas-module.json): enroll_key (Ed25519 public key), independent-host inventory, inspect, host approve, signed admit (prepared), activate — receipt serving_now: true, applied_shards == expected_shards (2/2). Negatives held: read-only inspect -> 403, tampered statement binding -> 400, flipped signature bit -> 403. revoke failed the interlay closed (503, nothing forwarded, zero backend effects); a fresh host approval plus a newly signed activation restored the module.

Honest scope

The fixture replicates the delivery shape with harmless marker effects; no real vendor component runs here. Bypass variants (encodings, alternate routes, other crafted members) are not bypass-tested and are out of scope; the rule is a lab-grade partial by construction. The vendor's fixed release remains the durable repair.

Cost record (per-CVE ledger)

$0.0000 USD (marginal session growth: 0 in + 0 out tokens, 0 turns; shared windows overlap).

Evidence appendix

  • cve.now entry
  • Raw evidence: gabriel-2026-11-11/module2/evidence/railsas-module.json
  • Deployment: installed eden-product-release:1.103.0-single lab container (image sha256:809293e1f0b525ac04698d1fa8d7ffdc66cbee1ff690b082ba26b4d050f08416)
  • Skill: eden-vulnerability-remediation

Decision flow

Decision diagram
Rendering diagram...

The diagram sketches the available source’s content decision after input metadata is available. Continue is a module decision; the host and backend still determine final forwarding and effects. This available source has a different hash from the source identity recorded for the tested binary.

Complete native module source

The complete available source for gabriel_railsas_v1 is reproduced below from the reviewed repository snapshot. The source SHA-256 is 052b015f09e4e305fb365152ff8a352b6efeceaabf71fc4d79b66b8d2328b2eb. Source identity mismatch: this case records 9c2d94a522965d249066ef4e14dc6776686827d53f2d2b9ce4f19032eb0413bb, which differs from the available listing. The listing cannot be identified as the exact source used to build the tested binary without further evidence. The case’s experiment status and limitations still apply, including any failed import, partial coverage, or withdrawn claim. Original source comments are preserved verbatim.

Rust
//! Gabriel module: refuse dangerous ImageMagick coder content (CVE-2026-126082 shape)
//!
//! CVE-2026-126082: Rails Active Storage processing uploads with
//! ImageMagick under a coder policy that does not deny them lets an
//! unauthenticated attacker write/read arbitrary files.
//! This module refuses upload bodies carrying dangerous-coder
//! content markers (MVG command sequences, script-bearing SVG); ordinary
//! attachments pass unchanged. no_std; no allocator, storage, background work or state.

#![no_std]

use core::mem::size_of;
use core::panic::PanicInfo;
use eden_gateway_remediation::message::{Availability, MessageInput};
use eden_gateway_remediation::module::{
    ApiHeader, CONTINUE, Decision, EDIT, EditSink, Edits, Failure, ModuleOutput, NEED_MORE, REFUSE, RemediationModule,
};
use eden_gateway_remediation::native_http_v3::{self as http, Api, Input, Request};

/// Route prefix in scope.
const ROUTE: &[u8] = b"/rails/active_storage/";

/// The byte primitives this module refuses on that route.
static NEEDLES: [&[u8]; 3] = [b"push graphic-context", b"<svg", b"<script"];


fn find_ignore_case_vol(haystack: &[u8], needle: &[u8]) -> Option<usize> {
    if haystack.len() < needle.len() || needle.is_empty() {
        return None;
    }
    let mut i = 0usize;
    while i + needle.len() <= haystack.len() {
        let mut j = 0usize;
        let mut same = true;
        while j < needle.len() {
            let h = unsafe { core::ptr::read_volatile(&haystack[i + j]) };
            let n = unsafe { core::ptr::read_volatile(&needle[j]) };
            if h.to_ascii_lowercase() != n.to_ascii_lowercase() {
                same = false;
                break;
            }
            j += 1;
        }
        if same {
            return Some(i);
        }
        i += 1;
    }
    None
}

fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
    if haystack.len() < needle.len() || needle.is_empty() {
        return None;
    }
    let mut i = 0usize;
    while i + needle.len() <= haystack.len() {
        let mut j = 0usize;
        let mut same = true;
        while j < needle.len() {
            if haystack[i + j] != needle[j] {
                same = false;
                break;
            }
            j += 1;
        }
        if same {
            return Some(i);
        }
        i += 1;
    }
    None
}

fn find_ignore_case(haystack: &[u8], needle: &[u8]) -> Option<usize> {
    if haystack.len() < needle.len() || needle.is_empty() {
        return None;
    }
    let mut i = 0usize;
    while i + needle.len() <= haystack.len() {
        let mut j = 0usize;
        let mut same = true;
        while j < needle.len() {
            if haystack[i + j].to_ascii_lowercase() != needle[j].to_ascii_lowercase() {
                same = false;
                break;
            }
            j += 1;
        }
        if same {
            return Some(i);
        }
        i += 1;
    }
    None
}

struct gabriel_railsas_v1;

impl RemediationModule<Request<'_>> for gabriel_railsas_v1 {
    fn evaluate(&self, request: &Request<'_>, edits: &mut Edits<'_>) -> Result<Decision, Failure> {
        let _ = edits;
        let _ = match request.header_count() {
            Availability::Ready(count) => count,
            Availability::NeedMore => return Ok(Decision::NeedMore),
            Availability::Unavailable => return Err(Failure),
        };
        let message = request.message();
        let head = message.prefix();
        let line_end = head.iter().position(|&b| b == b'\r').unwrap_or(head.len());
        let request_line = &head[..line_end];
        let path_at = request_line.iter().position(|&b| b == b' ').unwrap_or(request_line.len());
        let target = &request_line[path_at + 1..];
        if !target.starts_with(ROUTE) {
            return Ok(Decision::Continue);
        }
        if !message.is_complete() {
            return Ok(Decision::NeedMore);
        }
        let body_range = match request.body_range() {
            Availability::Ready(range) => range,
            Availability::NeedMore => return Ok(Decision::NeedMore),
            Availability::Unavailable => return Err(Failure),
        };
        let body = match message.bytes(body_range)? {
            Availability::Ready(bytes) => bytes,
            Availability::NeedMore => return Ok(Decision::NeedMore),
            Availability::Unavailable => return Err(Failure),
        };
        if NEEDLES.iter().any(|n| find_ignore_case(body, n).is_some()) {
            return Ok(Decision::Refuse);
        }
        Ok(Decision::Continue)
    }
}

static API: Api = Api {
    header: ApiHeader { version: http::VERSION, table_size: size_of::<Api>() as u32 },
    input_size: size_of::<Input>() as u32,
    output_size: size_of::<ModuleOutput>() as u32,
    sink_size: size_of::<EditSink>() as u32,
    message_size: size_of::<MessageInput>() as u32,
    state_bytes: 0,
    capabilities: CONTINUE | REFUSE | NEED_MORE | EDIT,
    required_inputs: 0,
    evaluate: Some(evaluate),
};

unsafe extern "C" fn evaluate(input: *const Input, sink: *const EditSink, output: *mut ModuleOutput) -> i32 {
    unsafe { http::invoke(&gabriel_railsas_v1, input, sink, output) }
}

#[unsafe(no_mangle)]
pub extern "C" fn eden_http_remediation_v3() -> *const Api {
    &API
}

#[panic_handler]
fn panic(_information: &PanicInfo<'_>) -> ! {
    loop {
        core::hint::spin_loop();
    }
}
Official CVE record