Sample
nimble_csv 1.3.0: Reassemble arbitrary CSV chunks and configure formula-prefix escaping in NimbleCSV 1.3.0
Verified sample for hex nimble_csv 1.3.0: Reassemble arbitrary CSV chunks and configure formula-prefix escaping in NimbleCSV 1.3.0. The contract ran on…
sha256:d3b935a442e69a84c6664c75e2a07c5c6354c86243d8f2f795aa07d17eb5619c
This network offers one thing: a sample that builds. It ran the sample in a sandbox and kept the signed receipt. It grades nothing and warrants nothing — whether the same code builds where you are is not something it measured.
How many distinct signing keys filed a passing contract receipt. One is the author alone; more than one means somebody else built it too. A key is self-generated with nothing registered behind it, so it counts keys, not people.
MIT-0
Execution evidence
The declared environment and the signed runs are kept apart, so you can see exactly what this sample ran and where.
- Evidence basis
- Signed contract pass
- Verification receipts
- 2
- Signing keys that built it
- 2
Declared environment
elixir 1 linux x64 elixir 1 elixir mix
Verification-run environments
| Environment | Contract | Stages | Run |
|---|---|---|---|
| elixir 1 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:PASS · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · hex@1 |
2026-08-17 |
| elixir 1 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:PASS · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · hex@1 |
2026-08-18 |
Case
HOW- Goal
- Reassemble arbitrary CSV chunks and configure formula-prefix escaping in NimbleCSV 1.3.0
- Packages
- Symbols
-
- NimbleCSV.define
- NimbleCSV.RFC4180.to_line_stream
- NimbleCSV.RFC4180.parse_stream
- NimbleCSV.RFC4180.dump_to_iodata
- NimbleCSV.Spreadsheet.to_line_stream
- Environment
- elixir 1
- Created
- 2026-08-17T04:44:18Z
Contract
- assert parsing the arbitrary chunks directly does not yield the complete expected table while RFC4180.to_line_stream reconstructs five complete lines and enables that parse
- assert Spreadsheet.to_line_stream reconstructs the tested UTF-16LE chunks into three tab-separated rows
- assert RFC4180.dump_to_iodata leaves the tested formula prefixes unchanged
- assert custom modules defined with escape_formula apply the configured quote or tab prefixes while preserving non-formula cells
Files
- NOTES.md
- csx.json
- lib/nimble_csv_stream/parsers.ex
- mix.exs
- mix.lock
- test/contract_test.exs
- test/test_helper.exs
Source
# NimbleCSV 1.3.0 chunking and formula prefixes
The contract measures arbitrary UTF-8 chunks with `RFC4180.to_line_stream/1`,
UTF-16LE chunks with `Spreadsheet.to_line_stream/1`, the default dumper's
verbatim formula prefixes, and two custom `escape_formula` mappings. It is
fully in memory and makes no assertion about earlier releases.
{"buildCommand":["mix","deps.compile","--no-deps-check"],"case":{"believed":"NimbleCSV.parse_stream reassembles arbitrary byte chunks into complete lines, and the default dumper prefixes spreadsheet formula triggers.","caseId":"case:sha256:fc135ed00444df51c153cf9a5bee5e33086800b1241676f9adb384702dc773e2","contract":["assert parsing the arbitrary chunks directly does not yield the complete expected table while RFC4180.to_line_stream reconstructs five complete lines and enables that parse","assert Spreadsheet.to_line_stream reconstructs the tested UTF-16LE chunks into three tab-separated rows","assert RFC4180.dump_to_iodata leaves the tested formula prefixes unchanged","assert custom modules defined with escape_formula apply the configured quote or tab prefixes while preserving non-formula cells"],"goal":"Reassemble arbitrary CSV chunks and configure formula-prefix escaping in NimbleCSV 1.3.0","kind":"HOW","packages":["pkg:hex/nimble_csv@1.3.0"],"schemaVersion":1,"symbols":["NimbleCSV.define","NimbleCSV.RFC4180.to_line_stream","NimbleCSV.RFC4180.parse_stream","NimbleCSV.RFC4180.dump_to_iodata","NimbleCSV.Spreadsheet.to_line_stream"]},"contractCommand":["mix","test","--no-deps-check"],"environment":{"arch":"x64","ecosystem":"hex","executionContext":"elixir","language":"elixir","os":"linux","packageManager":"mix","runtime":"elixir","runtimeVersion":"1","schemaVersion":1},"license":"MIT-0","packages":["pkg:hex/nimble_csv@1.3.0"],"schemaVersion":1,"symbols":["NimbleCSV.define","NimbleCSV.RFC4180.to_line_stream","NimbleCSV.RFC4180.parse_stream","NimbleCSV.RFC4180.dump_to_iodata","NimbleCSV.Spreadsheet.to_line_stream"],"verifierAdapter":"hex@1"}
defmodule NimbleCsvStream.Parsers do
@moduledoc """
Defines CSV parsers and dumpers demonstrating stream line chunking and formula escaping.
"""
# Custom dumper configured with formula escaping according to OWASP CSV injection guidelines.
# Values starting with =, +, -, @, \t, or \r are prefixed with a single quote.
NimbleCSV.define(NimbleCsvStream.FormulaSafeCSV,
separator: ",",
escape: "\"",
line_separator: "\r\n",
escape_formula: %{["@", "+", "-", "=", "\t", "\r"] => "'"}
)
# Custom dumper with custom tab-prefix formula escaping for spreadsheet calculation neutralisation.
NimbleCSV.define(NimbleCsvStream.TabEscapedCSV,
separator: ",",
escape: "\"",
line_separator: "\n",
escape_formula: %{~w(@ +) => "'", ~w(- =) => "\t"}
)
end
defmodule NimbleCsvStream.MixProject do
use Mix.Project
def project do
[
app: :nimble_csv_stream,
version: "0.1.0",
elixir: "~> 1.15",
deps: deps()
]
end
def application, do: [extra_applications: []]
defp deps do
[{:nimble_csv, "1.3.0"}]
end
end
%{
"nimble_csv": {:hex, :nimble_csv, "1.3.0", "b7f998dc62b222bce9596e46f028c7a5af04cb5dde6df2ea197c583227c54971", [:mix], [], "hexpm", "41ccdc18f7c8f8bb06e84164fc51635321e80d5a3b450761c4997d620925d619"},
}
defmodule NimbleCsvStreamTest do
use ExUnit.Case
alias NimbleCSV.RFC4180, as: CSV
alias NimbleCSV.Spreadsheet
alias NimbleCsvStream.FormulaSafeCSV
alias NimbleCsvStream.TabEscapedCSV
test "parse_stream/1 requires to_line_stream/1 for arbitrary byte chunks, which reassembles lines lazily" do
raw_chunks = [
"header_a,header_b\n",
"alice,25\nbo",
"b,30\ncarol",
",35\ndavid,40\n"
]
naive_parsed = raw_chunks |> CSV.parse_stream(skip_headers: false) |> Enum.to_list()
refute naive_parsed == [
["header_a", "header_b"],
["alice", "25"],
["bob", "30"],
["carol", "35"],
["david", "40"]
]
line_stream = CSV.to_line_stream(raw_chunks)
assert Enum.to_list(line_stream) == [
"header_a,header_b\n",
"alice,25\n",
"bob,30\n",
"carol,35\n",
"david,40\n"
]
assert raw_chunks
|> CSV.to_line_stream()
|> CSV.parse_stream(skip_headers: false)
|> Enum.to_list() == [
["header_a", "header_b"],
["alice", "25"],
["bob", "30"],
["carol", "35"],
["david", "40"]
]
end
test "to_line_stream/1 works with multibyte UTF-16LE encoded spreadsheet streams" do
to_utf16le = fn string ->
:unicode.characters_to_binary(string, :utf8, {:utf16, :little})
end
raw_chunks =
[
"col1\tcol2\n",
"val1\tva",
"l2\nval3\tval4\n"
]
|> Stream.map(to_utf16le)
parsed =
raw_chunks
|> Spreadsheet.to_line_stream()
|> Spreadsheet.parse_stream(skip_headers: false)
|> Enum.to_list()
assert parsed == [
["col1", "col2"],
["val1", "val2"],
["val3", "val4"]
]
end
test "dump_to_iodata does not escape formulas by default, leaving injection prefixes verbatim" do
rows = [
["name", "command"],
["user1", "=SUM(1,2)"],
["user2", "@cmd:run"],
["user3", "+12345"],
["user4", "-99999"]
]
dumped = rows |> CSV.dump_to_iodata() |> IO.iodata_to_binary()
assert dumped ==
"name,command\r\n" <>
"user1,\"=SUM(1,2)\"\r\n" <>
"user2,@cmd:run\r\n" <>
"user3,+12345\r\n" <>
"user4,-99999\r\n"
end
test "custom parser with :escape_formula prepends prefix to formula cells and preserves non-formulas" do
rows = [
["name", "formula_col", "clean_col"],
["user1", "=SUM(A1:B1)", "plain_text"],
["user2", "@calc", "100"],
["user3", "+44123456", "safe_val"],
["user4", "-500", "another_safe"]
]
dumped_tab = rows |> TabEscapedCSV.dump_to_iodata() |> IO.iodata_to_binary()
assert dumped_tab ==
"name,formula_col,clean_col\n" <>
"user1,\t=SUM(A1:B1),plain_text\n" <>
"user2,'@calc,100\n" <>
"user3,'+44123456,safe_val\n" <>
"user4,\t-500,another_safe\n"
dumped_owasp = rows |> FormulaSafeCSV.dump_to_iodata() |> IO.iodata_to_binary()
assert dumped_owasp ==
"name,formula_col,clean_col\r\n" <>
"user1,\'=SUM(A1:B1),plain_text\r\n" <>
"user2,\'@calc,100\r\n" <>
"user3,\'+44123456,safe_val\r\n" <>
"user4,\'-500,another_safe\r\n"
end
end
ExUnit.start()