v1.0.0-rc15
Pre-release
Pre-release
·
18 commits
to main
since this release
Security
- Scoped
id-token: write(andpackages/contents: write) from workflow
level down to the specific jobs that need them
(.github/workflows/ci.yml,
image.yml,
release.yml). All three workflows granted
id-token: writeat workflow scope, so every job in them could mint a
Sigstore-attributable OIDC token — including jobs that never sign. Job-level
permissionsblocks replace workflow inheritance, so the token now reaches
only the Cosign signing jobs (check,image,build); the publish job
(release) keepscontents: writebut notid-token, and any future job
added without a block inherits the minimalcontents: readdefault. Surfaced
by the pre-red-team security assessment (finding SC-3). No image change. - Bumped
google.golang.org/grpcv1.80.0 → v1.82.1 to clear a High
Dependabot alert (GHSA gRPC-Go xDS RBAC / HTTP/2, CVSS 8.8). gRPC is an
indirect dependency pulled transitively viagooseand the OTLP/gRPC
telemetry exporters. The advisory affects gRPC's xDS RBAC authorization engine
and HTTP/2 transport server; this server never runs a gRPC server and does
not use xDS (our only gRPC use is the OTLP exporter client), so the vulnerable
code is not reachable — this is a hygiene bump to keep the public alert page
clean. Pulledgolang.org/x/oauth2andgenproto/googleapis/apiforward in
step (MVS; both permissively licensed). No first-party code change. - Made the Docker image build hermetic (Dockerfile). The
builder stage now compiles with-mod=vendoragainst the committedvendor/
tree and no longer runsgo mod download, so the image build never contacts
the module proxy. This removes the proxy-flake failure mode that hit PR #47's
image job and makes the compiled dependency set supply-chain-deterministic
(what is reviewed invendor/is exactly what is built). No change to the
compiled binary. Spotted during the pre-red-team security assessment. - Masked the caller email in the public key-request log line
(internal/mcp/keys_request_http.go). The
unauthenticatedPOST /api/v1/keys/requesthandler logged the full requester
email at INFO; it now logs only a masked form (a***@example.com) via a new
logging.SafeEmailhelper.request_idalready links to the full stored
record, so no operator capability is lost. Surfaced by the security assessment
(finding LG-3). - Bounded the upstream broadcast error persisted to the write-audit trail
(internal/mcp/tools_evm_write.go). The
node-returned error string on a failed broadcast is now capped at 512
characters before it reacheswrite_audit.Erroror the audit log. The client
never sees this error (SafeForClientcollapses it), but an unbounded reply
from a hostile/MITM'd node could otherwise bloat the audit column and log sink.
Surfaced by the security assessment (finding LG-2). KEY_HMAC_PEPPERnow has a boot-time length floor
(internal/config/config.go). A set pepper
(active or previous) shorter than 32 characters fails boot with
ErrPepperTooShort. Previously any non-empty value was accepted, so a weak,
brute-forceable pepper silently weakened the key-hash MAC while giving the
false confidence of "peppered." Unset (unpeppered) mode is unchanged. Operators
running a pepper shorter than 32 characters must rotate to a ≥ 32-character
high-entropy secret before upgrading. Surfaced by the pre-red-team security
assessment (finding KS-2).- Hardened the untrusted RPC-node boundary against denial of service
(internal/evm, internal/anchor). Node/RPC
responses are untrusted (a plaintexthttp://endpoint is permitted, so a
hostile or MITM'd node controls the bytes). Two gaps are closed: (EV-1) every
response body is now capped at 32 MiB via a limitinghttp.RoundTripper, so an
unbounded reply can no longer exhaust process memory before decode; and (EV-2)
the node-response decode/normalize paths now run underrecover(), converting
a malformed-response panic into anErrNodeResponseDecodeerror instead of
crashing the stdio process. This mirrors therecover()+ size cap already on
the caller-transaction decode path and satisfies supplement invariant INV-6.
Surfaced by the pre-red-team security assessment (findings EV-1, EV-2). - Fuzz-hardened the untrusted decode boundary
(internal/evm/decode_fuzz_test.go,
internal/anchor/decode_fuzz_test.go).
Go native fuzz targets for the caller-tx decode (11.3M execs) and the anchor
ABI decode (3.4M execs). The caller path held every invariant (no panic
escapesrecover();CanonicalRawis a signer fixed-point — the
parser-differential defense). Fuzzing also corrects an earlier
characterization: thedefiwebABI decoder is not bounds-checked — an
unguarded probe panicked it in 1.6s with a 29-byte input (slice bounds out of range [-8388608:], a length prefix decoded as a negative index). The EV-2
anchor guard therefore defends a real, trivially-craftable hostile-node DoS,
not a hypothetical one; that crashing input is now a regression seed. - Pinned the CI license-check tool:
go-licenses@latest→@v1.6.0
(.github/workflows/ci.yml). An unpinned
go install ...@latestresolves to whatever the module proxy serves at run
time and executes arbitrary Go on the runner — a supply-chain sink,
particularly in a job that carriesid-token: write. It sat directly beside a
deliberately-pinnedgovulncheck@v1.3.0; this closes the asymmetry. v1.6.0
verified to run clean under the pinned Go toolchain. Surfaced by the pre-red-team
security assessment (finding SC-2). No image change.
Fixed
- Corrected an over-broad logging claim in the README. The Observability
section stated sensitive data "is redacted in all log output"; in fact the
SafeAddr/SafeURL/SafeTxDatahelpers redact on debug lines, while the
write-audit broadcast lines (INFO/WARN) deliberately record the recovered
on-chain signer, destination, and value in full (public on-chain data forming
the audit trail). README now matches the code and cross-references
docs/DATA_HANDLING.md. Surfaced by the security assessment (finding LG-1,
doc leg). The in-code posture is now resolved (see Documentation below):
logging these identifiers is retained by design; the log sink's retention is
documented as the operator's responsibility.
Documentation
- Corrected the audit-attribution claim in
docs/SECURITY_CONSUMER_GUIDANCE.md
(finding CG-1). "Trace which API key created or modified an offending record"
was false: the write-audit is keyed by the recovered on-chain signer (there
is noclient_idcolumn, and anonymous keyless writes carry no API key). The
claim now describes signer-based attribution, with the authenticated caller's
client_idin the audit log line. - Documented the write-audit log sink as outside the retention purge
(finding LG-1,docs/DATA_HANDLING.md§ 8.3). The in-process purge deletes DB
rows only; the same signer/destination/value appear in structured audit log
lines governed by the operator's log-pipeline retention. Logging them is
deliberate (operator abuse-forensics; values are already public on-chain) and
accepted — a published retention period must configure log retention to match. - Documented the per-signer quota as a soft ceiling under concurrency
(finding EA-1,docs/DATA_HANDLING.md§ 8.2 +SignerQuotaStoregodoc). The
non-atomicCount→Incrementcan over-admit pastMCP_SIGNER_WRITE_RATEby
roughly the concurrency width; an accepted trade-off for a coarse, gas-bounded
anti-abuse throttle (an exact cap would need a per-signer lock on every
broadcast). No behavior change.
Verifying signatures
Each binary is shipped with a Cosign keyless signature (via Sigstore OIDC) and a SHA-256 checksum.
# Verify checksum
shasum -a 256 -c nvnm-mcp-server-v1.0.0-rc15-<os>-<arch>.sha256
# Verify Cosign signature
cosign verify-blob \
--certificate nvnm-mcp-server-v1.0.0-rc15-<os>-<arch>.cert.pem \
--signature nvnm-mcp-server-v1.0.0-rc15-<os>-<arch>.sig \
--certificate-identity-regexp 'https://github.com/NVNM-Chain/nvnm-mcp-server/.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
nvnm-mcp-server-v1.0.0-rc15-<os>-<arch>