fix(security): use the public Bytes API instead of the constructor - #7
Merged
Merged
Conversation
march-language/march#247 changes Bytes from a cons list to a contiguous buffer (`Bytes(String)`). Five sites here reached through the constructor and break against it: crypto.march bytes_to_hex / bytes_to_string destructured `Bytes(xs)` crypto.march built `Bytes(byte_list)` directly hkdf.march built `Bytes(Nil)` and `Bytes(Cons(i, Nil))` All five now go through `Bytes.to_list` / `from_list` / `to_string` / `empty`, which exist in both representations and behave identically — verified by running the same program under a cons-list toolchain and an array-backed one. bytes_to_string collapses to `Bytes.to_string`, which it was reimplementing; since #247 that is a single buffer read rather than a per-byte walk. Backward compatible: 369 tests pass on the current toolchain. NOTE: this is necessary but not sufficient for bastion to build against march main. bastion still declares the pre-split capability names (`needs IO.File, IO.Dir`) and hits the capability ceiling on IO.FileRead/IO.FileWrite across Bastion, Static, Forge.Lower and Forge.BuildIslands. That migration is separate from this change.
bastion did not build against march `main` for reasons independent of the
Bytes change in the previous commit. March enforces capabilities in three
separate places, and bastion tripped all three:
1. typecheck "function body calls a builtin that requires Cap(X)"
2. codegen "CAPABILITY CEILING: a module's emitted code must stay
within its own needs" — 13 violations, mostly file I/O
3. imports "module M imports N which requires Cap(X)" — propagation,
which is why BastionServer needs IO.Process purely for
importing Bastion
Also drops `needs IO.File, IO.Dir` from the test module. Those are not
capabilities in march's lattice — it has IO.FileRead, IO.FileWrite and
IO.FileSystem — so the declaration never granted anything, which is why the
file-I/O ceiling violations appeared despite it looking like they were
covered.
16 lines across 7 files. Every one is exactly what the compiler named; none
is wider. No behaviour change — these declare authority the code already
exercises.
Verified against march main (cbb8346e, the revision CI resolves): 369 tests,
0 failures.
Ch4s3
added a commit
that referenced
this pull request
Aug 11, 2026
Ships #7: the public-Bytes-API migration for march#247 (array-backed Bytes), and the capability declarations march main now enforces at typecheck, codegen ceiling, and import propagation. Needed as a RELEASE, not just a merge — same reason as 0.2.4: forgepm resolves bastion from the registry (`bastion = "0.2.4"`), so its CI still typechecks the published 0.2.4 sources and fails on exactly the errors #7 fixed. A git dep is not an alternative here: this repo is private and forgepm's CI clones git deps anonymously. Verified against march main (cbb8346e): 369 tests, 0 failures.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Unblocks march#247 (array-backed
Bytes), which is merged on marchmain.Why
#247 changes
type Bytes = Bytes(List(Int))totype Bytes = Bytes(String)— a contiguous buffer instead of a cons list. Five sites inlib/security/reach through the constructor and break against it:crypto.marchbytes_to_hexdestructuredBytes(xs)crypto.marchbytes_to_stringdestructuredBytes(xs)crypto.marchBytes(byte_list)directlyhkdf.marchBytes(Nil)(twice)hkdf.marchBytes(Cons(i, Nil))What changed
All five go through the public API —
Bytes.to_list,Bytes.from_list,Bytes.to_string,Bytes.empty— which exists in both representations with identical behaviour.bytes_to_stringcollapses toBytes.to_string, which it was reimplementing by hand. Since #247 that is a single buffer read rather than a per-byte walk.Verification
Backward compatible: 369 tests, 0 failures on the current toolchain.
Forward compatible: the exact API surface this migrates to produces identical output under both representations —
(Run directly rather than through bastion's suite, because of the caveat below.)
Caveat — necessary but not sufficient
This does not make bastion build against march
main. bastion still declares the pre-split capability names (needs IO.File, IO.Dir) and hits the capability ceiling onIO.FileRead/IO.FileWriteacrossBastion,Static,Forge.Lower,Forge.BuildIslands, and the test module. That migration is a separate change and is not attempted here.So the ordering for anything downstream is: this PR → bastion's capability migration → a bastion release → consumers bump their toolchain.