Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ is `Outer`, which has no error result: it panics when its result is too big,
which takes operands of more than 2^30 elements each. An empty array whose
other axis is huge, `(2^58, 0)`, costs nothing to reduce, concatenate or scan.

A shape that is representable can still be far larger than memory, and Go
cannot recover from running out of it: the process dies, where NumPy raises
`MemoryError`. Several operations produce more than they are given:
broadcasting (`(n, 1) + (1, n)` is n² elements), `Outer`, `MatMul`, and summing
the empty axis of `(0, n)` (n zeros, as in NumPy). When shapes come from
someone else, bound the result's size before computing it.

The assembly kernels take raw pointers. Their Go wrappers check every operand
the kernel reads or writes against the length it is given, so a mistake is an
index panic in Go, never an access outside a slice; a test places each operand
Expand All @@ -156,6 +163,7 @@ cols, _ := m.SumAxis(0, false) // sum down each column -> [3 5 7]
// NumPy basic-indexing views (share data):
col0, _ := m.Slice(ndarray.All(), ndarray.A(0)) // m[:,0] -> [0 3]
sub, _ := m.Slice(ndarray.R(0, 2), ndarray.Step(2)) // m[0:2, ::2]
row1, _ := m.Slice(ndarray.A(1)) // m[1]: unindexed axes are whole

// ufuncs and masks
roots := m.Sqrt()
Expand Down
29 changes: 29 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,40 @@ bounds checks do not cover, so they are held to the following:
`TestEmptyArraysWithHugeAxesReturnPromptly` covers concatenation, the axis
reductions and the scans.

- **Memory is the caller's to bound.** Validation guarantees a shape is
representable, not that it fits in memory, and a Go program that runs out of
memory dies (NumPy raises `MemoryError`). Broadcasting, `Outer`, `MatMul` and
summing the empty axis of `(0, n)` produce more than their operands hold;
with untrusted shapes, check the result's size first.
- **Fuzzed against an oracle.** `FuzzOps` (`fuzz_test.go`) drives slices,
reshapes, transposes, squeezes, broadcasting arithmetic, axis reductions,
`Take`, `MaskSelect`, concatenation, `MatMul` and scans from arbitrary bytes,
and compares every result element by element with an oracle written in the
test from NumPy's definitions (Python's `slice.indices`, the broadcasting
rule, ...). The library must reject exactly the calls the oracle rejects and
never panic.

The minimum Go version (`go` in `go.mod`) is kept on a release that fixes the
standard library's known vulnerabilities; `govulncheck ./...` reports none.

## Audit log

**2026-10-04 (v0.4.0).**

- `FuzzOps` ran 47 million executions without a divergence. A first version
that only checked an array against its own `Copy` was **blind**: with
`Squeeze` deliberately given wrong strides it found nothing in 31 million
executions, because a wrong view reads the same wrong values both ways. The
oracle version finds the same sabotage in 11 seconds; its failing input is
kept in `testdata/fuzz/FuzzOps`.
- It found two places where the library and NumPy differ. `Slice` with fewer
indices than axes was an error; it now takes the rest whole, as NumPy does.
`MaskSelect` accepts a mask that broadcasts to the array, which NumPy
rejects; this is kept as a documented extension.
- `SumAxis`/`ProdAxis` along a zero-length axis now return 0s/1s as NumPy does
(they were errors, unlike `Sum`/`Prod` of an empty array). This makes the
memory caveat above concrete, and it is documented there.

**2026-10-04 (v0.3.0).**

- `govulncheck` found no reachable vulnerability. At `go 1.26.4`, 10 standard
Expand Down
Loading
Loading