Skip to content

feat(golang): se_last_error gives Go the full error behind a guest's status number, with a leak test - #1104

Open
coderdan wants to merge 3 commits into
claude/gracious-einstein-ywrwimfrom
claude/gracious-einstein-ywrwim-guest-errors
Open

coderdan wants to merge 3 commits into
claude/gracious-einstein-ywrwimfrom
claude/gracious-einstein-ywrwim-guest-errors

Conversation

@coderdan

@coderdan coderdan commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Go's Rust WebAssembly runtimes can now return the full error behind a failed call, including its code, message, help, fields, and causes. Previously, Go received only a status number, losing the details added in #1103. The existing status numbers remain unchanged; the new se_last_error export makes details available for the Go integration in #1110.

This is the second of three PRs for #1098. It covers both the encryption runtime and the authentication runtime, which manages login files and access tokens.

Changes

  • Shared transport: Adds error encoding in stack-guest-abi, the shared runtime interface. The encoded object contains code, message, optional help and URL, severity, structured fields, and causes. External-library causes contribute approved details, such as an I/O error kind or JSON position, rather than their raw messages.
  • Error lifecycle: The common export wrapper clears the previous error at the start of a call and records a fallback if a failure has no library error. se_last_error transfers the recorded buffer to the host and empties the slot; normal deallocation or shutdown wipes the buffer.
  • Encryption runtime: Records library errors and boundary validation failures while returning the existing status. Configuration errors name the key without its value. (Invalid stored EQL (Encrypt Query Language) values no longer repeating parser messages is now done in feat(stack-encrypt)!: codes, help and fields on every error in stack-encrypt, stack-kms, stack-auth and stack-profile #1103; the leak test here still checks it.)
  • Authentication runtime: Records authentication and profile errors. Status mapping now matches error variants instead of code strings, with tests preserving the previous status assignments. Invalid strategy JSON uses a fixed message because it can contain an access key.
  • Leak regression tests: Drives reachable native error paths with markers for plaintext, context, ciphertext, keys, tokens, and response bodies. Tests search the complete decoded error, including object keys, and pin which codes were exercised.

Verification

Run after restacking onto #1103 at b19f4778 (head c800c83a), using Rust 1.94.1:

  • Rust: cargo test -p stack-guest-abi (16 passed); both runtimes' native suites, including the leak tests, passed in every configuration: encryption default, deterministic-kms, eql, eql,deterministic-kms, and authentication. cargo fmt --check and clippy with -D warnings passed for stack-guest-abi (native and wasm32) and for both runtimes (native and wasm32, every configuration).
  • Built artifacts: All five release WebAssembly builds passed scripts/check-wasm-imports.py with the existing import rules.
  • Go: CGO_ENABLED=0 go test ./... from languages/golang passed against the freshly built runtimes (checked on feat(golang): every failure a guest reports is a Diagnostic that unwraps to its sentinel #1110's tree, which adds only Go code to this one). go vet ./... and gofmt -l were clean.
  • Not run locally: Miri for stack-guest-abi (requires nightly; covered by CI) and credential-dependent live tests. CI on the new head has not been checked.

Related

Review notes

🤖 Generated with Claude Code

https://claude.ai/code/session_01URtfKsTToFUCRwq3g7gCUf

@changeset-bot

changeset-bot Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 474ba18

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

Only developers with an assigned seat can use this organization's usage-based review budget, and seats here are assigned manually. Ask an admin to assign a seat, or change the review continuation mode in Billing.

Next included review available in 52 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 37e8a91a-cd75-45f4-89cf-7acd34a48c4c
📥 Commits

Reviewing files that changed from the base of the PR and between c800c83 and 474ba18.

⛔ Files ignored due to path filters (3)
  • Cargo.lock is excluded by !**/*.lock
  • languages/golang/auth/guest/Cargo.lock is excluded by !**/*.lock
  • languages/golang/encrypt/guest/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • languages/golang/auth/guest/src/abi.rs
  • languages/golang/auth/guest/src/auth.rs
  • languages/golang/auth/guest/src/ops.rs
  • languages/golang/auth/guest/src/status.rs
  • languages/golang/auth/guest/tests/leak.rs
  • languages/golang/encrypt/guest/src/abi.rs
  • languages/golang/encrypt/guest/src/config.rs
  • languages/golang/encrypt/guest/src/ops.rs
  • languages/golang/encrypt/guest/src/options.rs
  • languages/golang/encrypt/guest/src/status.rs
  • languages/golang/encrypt/guest/tests/leak.rs
  • packages/stack-guest-abi/Cargo.toml
  • packages/stack-guest-abi/src/abi.rs
  • packages/stack-guest-abi/src/buffers.rs
  • packages/stack-guest-abi/src/last_error.rs
  • packages/stack-guest-abi/src/lib.rs

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 802e61df-a73c-4453-830b-c1c49421afba
📥 Commits

Reviewing files that changed from the base of the PR and between a2cca12 and fcac251.

⛔ Files ignored due to path filters (3)
  • Cargo.lock is excluded by !**/*.lock
  • languages/golang/auth/guest/Cargo.lock is excluded by !**/*.lock
  • languages/golang/encrypt/guest/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • languages/golang/auth/guest/src/abi.rs
  • languages/golang/auth/guest/src/auth.rs
  • languages/golang/auth/guest/src/ops.rs
  • languages/golang/auth/guest/src/status.rs
  • languages/golang/auth/guest/tests/leak.rs
  • languages/golang/encrypt/guest/src/abi.rs
  • languages/golang/encrypt/guest/src/config.rs
  • languages/golang/encrypt/guest/src/ops.rs
  • languages/golang/encrypt/guest/src/options.rs
  • languages/golang/encrypt/guest/src/status.rs
  • languages/golang/encrypt/guest/src/targets.rs
  • languages/golang/encrypt/guest/tests/leak.rs
  • packages/stack-guest-abi/Cargo.toml
  • packages/stack-guest-abi/src/abi.rs
  • packages/stack-guest-abi/src/buffers.rs
  • packages/stack-guest-abi/src/last_error.rs
  • packages/stack-guest-abi/src/lib.rs

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.


📝 Walkthrough

Walkthrough

The shared guest ABI now records structured error details for failed exports and exposes them through se_last_error. The auth and encryption guests use the shared error paths, and native tests check recorded errors for expected details and marker values.

Changes

Guest error reporting

Layer / File(s) Summary
Shared error storage and retrieval
packages/stack-guest-abi/Cargo.toml, packages/stack-guest-abi/src/*
The shared ABI adds diagnostic encoding, error-buffer lifecycle operations, the export wrapper, and se_last_error. Invalid input pointer and length pairs now record malformed-input errors.
Encryption guest export lifecycle
languages/golang/encrypt/guest/src/abi.rs
Encryption exports use the shared wrapper. Initialization, selector, and lifecycle errors now use descriptive errors or record underlying failure details. Shutdown clears the recorded error.
Encryption operation errors and validation
languages/golang/encrypt/guest/src/{config,ops,options,status,targets}.rs, languages/golang/encrypt/guest/tests/leak.rs
Operation, validation, and key-resolution failures record mapped or structured errors. Tests check error details, expected codes, and marker absence in encoded errors.
Auth guest error mapping and tests
languages/golang/auth/guest/src/{abi,auth,ops,status}.rs, languages/golang/auth/guest/tests/leak.rs
Auth and profile failures record error details; malformed inputs and handle-state errors use dedicated helpers. Tests check error codes, diagnostic fields, and marker absence.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant GoHost
  participant GuestExport
  participant SharedABI
  participant LastErrorBuffer
  GoHost->>GuestExport: call guest export
  GuestExport->>SharedABI: invoke export wrapper
  SharedABI->>LastErrorBuffer: clear prior error
  SharedABI->>LastErrorBuffer: record details when export fails
  GoHost->>SharedABI: call se_last_error after failure
  SharedABI->>LastErrorBuffer: transfer registered error buffer
  LastErrorBuffer-->>GoHost: return packed error
Loading

Merge Risk: ⚪ Minimal · up to fcac2

Failed calls from the encryption and authentication runtimes now include detailed error information. Existing status numbers are preserved, and the new details avoid quoting sensitive input. No concrete merge-blocking problems were identified.

Security Architecture Review

Security architecture risk: 🔵 Low · up to fcac2

The new diagnostic channel preserves existing status results and has explicit retrieval and cleanup rules. No introduced security defect was established in the inspected paths. Risk remains low rather than minimal because host-side consumption and complete sensitive-data coverage are not yet verified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The shared diagnostic contract affects both guest implementations, with pending diagnostics scoped to their serialized instance lifecycle. The inspected encryption caller supplies configured credentials before initialization; exposure through other embedders or downstream diagnostic sinks remains unresolved.

Trust Boundaries and Controls

  • observed — The pointer ABI relies on host ownership discipline rather than authenticating the raw caller. Linear-memory validation and Go-side allocation/deallocation bound the inspected integration path. Encryption initialization wipes validated client-key input before parsing and network initialization; these ownership responsibilities predate the new diagnostic channel.
  • observed — Foreign causes receive restricted descriptions or a fixed fallback, and traversal is limited to 16 causes. Root messages, structured fields, and diagnostic-source messages are copied under the producer content policy, which permits selected identifiers and descriptions but excludes secret data. This is not central enforcement of redaction.

Resilience and Maintainability Implications

  • observed — Marker-test source recursively checks selected encoded failures and pins reached error codes. Authentication strategy paths are partly represented by reconstructed errors, and host imports are not exercised natively. These checks support the controls but do not establish exhaustive input coverage or end-to-end disclosure behavior.

Hardening Proposals

  • proposed — When adding the host reader, keep the failed call and diagnostic retrieval in one serialized operation, verify zero-result and repeated-retrieval handling, and guarantee deallocation despite cancellation or decoding failure. Validate diagnostic exposure at any logging or outward-facing error boundary.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.54% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 142 functions across 16 files. (1 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #1100 requires shared se_last_error support, unchanged status behavior, error recording from both guests, safe FfiValue error details, and leak tests. The shared ABI wrapper clears and recor…
Out of Scope Changes check ✅ Passed The changes in the summary support issue #1100. The guest error mappings, sensitive-message handling, buffer lifecycle, and tests implement or verify its requirements. The dependency alignment support…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title identifies the main change: se_last_error exposes guest error details, and it mentions the leak test. The reference to Go describes the consumer, not a Go integration change in this PR.
Full details: Docstring Coverage

Explanation

Docstring coverage is 72.54% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 142 functions across 16 files. (1 skipped: 1 unsupported.)

  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Mutation testing (cargo-mutants, --in-diff, stack-auth + stack-encrypt)

caught missed unviable timeout
106 3 77 0

Surviving mutants: add a test that fails on each before merging

packages/stack-auth/src/error.rs:117:9: replace RequestError::is_no_transport -> bool with false
packages/stack-auth/src/error.rs:124:9: replace RequestError::message -> &'static str with ""
packages/stack-auth/src/error.rs:124:9: replace RequestError::message -> &'static str with "xyzzy"

@coderdan

coderdan commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto #1094 at 6efc22b, which fixes the second cipherstash-bot review there (index-only fields and nil interface passthroughs now round-trip). Two conflicts came up, in gensupport/declaration.go (#1095) and stack-encrypt/Cargo.toml (#1103, where sha2 is now gone and serde_json sits in [dependencies]). At the top of the stack the Go suite passes with all five guests built, and so do cargo nextest --workspace --all-features and clippy.

https://claude.ai/code/session_01V3WFXwax4J3uecpFEJ6yHc

@coderdan
coderdan force-pushed the claude/gracious-einstein-ywrwim-guest-errors branch 2 times, most recently from 2ef2e86 to 6b896fe Compare October 7, 2026 00:55
@coderdan
coderdan force-pushed the claude/gracious-einstein-ywrwim-guest-errors branch from 6b896fe to 647576f Compare October 7, 2026 01:39
@coderdan
coderdan force-pushed the claude/gracious-einstein-ywrwim-guest-errors branch from 647576f to 615570b Compare October 7, 2026 02:40
@coderdan

coderdan commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto the new #1103. That includes #1103's two docs commits (227e1fb, 39f2813), which this branch was missing before. No conflicts. Go tests pass locally against freshly built guests.

https://claude.ai/code/session_01V3WFXwax4J3uecpFEJ6yHc

@coderdan
coderdan force-pushed the claude/gracious-einstein-ywrwim-guest-errors branch 2 times, most recently from d8eaa93 to c3fb02e Compare October 7, 2026 03:54
@coderdan
coderdan force-pushed the claude/gracious-einstein-ywrwim-guest-errors branch from c3fb02e to 52f49a1 Compare October 7, 2026 04:00
@coderdan
coderdan force-pushed the claude/gracious-einstein-ywrwim-guest-errors branch 3 times, most recently from b6a9a08 to fcac251 Compare October 7, 2026 04:35
Comment on lines +56 to +58
/// What was wrong, naming the key and never its value: the config
/// carries the client key. An unknown key is not named either, since a
/// value pasted into the wrong place would arrive as one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very clumsily worded. Please rewrite to make clearer.

@coderdan
coderdan marked this pull request as ready for review October 7, 2026 04:50
@coderdan
coderdan requested a review from a team as a code owner October 7, 2026 04:50
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-07T04:53:22.072792Z fcac251 Draft marked ready
🔒 Security Review ✅ Completed 2026-10-07T04:56:09.235757Z fcac251 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderdan
coderdan requested a review from auxesis October 7, 2026 04:52
"a token whose claims are not the claims",
failure("claims", || {
use base64ct::{Base64UrlUnpadded, Encoding};
let claims = format!(r#"{{"sub": 7, "workspace": "{ACCESS_TOKEN}"}}"#);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This scenario cannot detect the leak that it is for. serde reads sub first and stops at invalid type: integer `7`, expected a string. The marker in workspace is never parsed, so it is never quoted.

I verified this. I changed stack_profile::diagnostic::describe_json_error to return error.to_string() (the raw serde message) and the encrypt leak test still passed. The auth leak test failed on its auth.json case, so the mutant was active.

Fix: put the marker where serde quotes it, for example:

let claims = format!(r#"{{"sub": "x", "iss": "x", "services": "{ACCESS_TOKEN}"}}"#);

With this change, the test fails on the mutant (a token whose claims are not the claims.message: leak-marker-access-token) and passes on the PR head. It still reaches stack_auth::invalid_token.

UnknownKey(String),
}

impl ConfigError {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ConfigError doc at line 33-35 is now false. It says the detail "is never surfaced across the boundary (statuses leak no config content)". describe() now sends the detail to the host through se_last_error (malformed(e.describe()) in cipher_init). Please change that doc to say that the key name crosses the boundary and the value does not.

coderdan commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Restacked onto #1103's new head (b19f4778) and force-pushed. The new head is c800c83a.

This rewrite changed more than commit hashes. #1103 now makes the stored-EQL fix itself, so this PR no longer changes languages/golang/encrypt/guest/src/targets.rs. The conflict there was resolved in favour of #1103's describe_json_error, because #1103's a_stored_value_that_does_not_parse_quotes_none_of_it test pins its exact wording. That helper also always returns a string, so the unwrap_or_else fallback is gone.

The second commit's message no longer claims that fix. The leak test still checks it. Nothing else changed.

Every guest feature set's tests and clippy pass, both natively and for wasm32. go test ./... passes against freshly built runtimes. The PR description has the details.


Generated by Claude Code

@coderdan
coderdan force-pushed the claude/gracious-einstein-ywrwim-guest-errors branch from c800c83 to 8498181 Compare October 7, 2026 11:15
…hind a status

A failing guest export returns one number from the shared status table,
and every Rust error behind it is folded away there: the code, message,
help and fields #1099 gave each error never reach Go (#1100). The status
number stays exactly as it is, the fast path a host acts on, byte-for-byte
the vitaminc guest's. Beside it, the full error is now kept for the host
to ask for.

`last_error` encodes an error as one value in the transport codec every
input and output already uses — `code`, `message`, `help`, `url`,
`severity`, `fields` (the error's `payload()`) and `causes`, a list of
`{code?, message}` — so there is no second encoding. The encoder takes any
miette diagnostic and its fields, so this crate still names no library a
guest is built over. A cause from another library contributes only what a
describer vouches for (an I/O error's kind, a JSON error's kind and
position), never its message.

`abi::export` is the one wrapper every guest export runs through: it
clears the last error when the export starts, makes sure one is recorded
when it fails (a `GuestError` for the status when the export recorded
nothing), and packs the result. `se_last_error` returns the error as a
buffer in the usual packed format, or zero. The error lives in the buffer
registry from the moment it is stored, so it is wiped like every other
buffer, by `se_dealloc` once the host has it, and by `wipe_all` at
shutdown; handing it over empties the slot. `input()` records which
pointer/length check failed.

Refs #1100

Claude-Session: https://claude.ai/code/session_01URtfKsTToFUCRwq3g7gCUf
…export, with a leak test

The crypto and credential guests now run every export through
`stack_guest_abi::abi::export`, so each exports `se_last_error` and every
failing export leaves an error for it. Every place that maps an error to a
status also records it: `status::fail_error`, `fail_dynamic`, `fail_auth`
and `fail_profile` record the stack-encrypt, stack-kms, stack-auth or
stack-profile error with its fields and return the status the old mapping
gave. Where a guest refuses input before any library sees it (bytes that
are not the codec, a malformed options object or config, a call out of
order) it records a `GuestError` naming what it refused, and the config
error names the key, never its value. The status numbers are unchanged
and so are their tests.

The credential guest's `status_for_auth` matches stack-auth's variants
instead of hand-typed `error_code()` strings, so a renamed code can no
longer fall through to "other auth failure"; a test pins that every
variant gets the status the string table gave it.

The leak tests (`tests/leak.rs` in each guest) drive every error path a
native test can reach with marker values where a caller's data would be —
plaintext, contexts, ciphertext, a client key, an access token or access
key, a ZeroKMS response body — and assert no marker appears in any
encoded error, at any depth, keys included. Each pins the codes it
reached, so a path that stops being driven fails there. Reverting either
the context-descriptor rule or the JSON-message rule makes them fail.

The credential guest's lockfile moves `vitaminc-aead-value` from 0.5.0 to
0.5.1, the version stack-guest-abi builds against and the crypto guest
already uses.

Refs #1100

Claude-Session: https://claude.ai/code/session_01URtfKsTToFUCRwq3g7gCUf
…kept list

The same change as #1103 makes for the stack crates: ERROR_CODES was read
only by its own test. The test now builds every GuestError through a
match with no wildcard, so a new variant fails to compile until it has a
row, and checks each code is in this crate's namespace and snake_case.

Refs #1100

Claude-Session: https://claude.ai/code/session_01URtfKsTToFUCRwq3g7gCUf
@coderdan
coderdan force-pushed the claude/gracious-einstein-ywrwim-guest-errors branch from 8498181 to 474ba18 Compare October 7, 2026 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Go guests return only a status number on failure — add se_last_error carrying the full error as an FfiValue, with a leak test

3 participants