Skip to content

feat: consolidate LK language, VM, AOT, and platform support - #32

Open
lollipopkit wants to merge 732 commits into
mainfrom
feat/aot-try-catch
Open

lollipopkit wants to merge 732 commits into
mainfrom
feat/aot-try-catch

Conversation

@lollipopkit

@lollipopkit lollipopkit commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

This branch started by removing try/catch from the AOT coverage allowlist and grew into a broad consolidation of LK's language implementation, VM, native backend, standard library, tooling, and bare-metal targets.

The result is a substantially wider and more consistently tested language surface across the interpreter and Cranelift paths. The branch contains 732 commits and changes 506 files; the sections below describe the main outcomes rather than every individual fix.

Highlights

Language and front end

  • Expanded expression-oriented control flow, pattern matching, closures, traits, structs, machine integers, defer, and first-class try/catch.
  • Strengthened parsing, name resolution, import handling, type checking, builtin signatures, default/named arguments, and diagnostics.
  • Extended declarative and procedural macro support, hygiene, origin tracking, expansion inspection, and LSP integration.
  • Consolidated user-visible semantics for equality, ordering, display, indexing, slicing, map keys, error values, and method lookup.

VM and runtime correctness

  • Reworked VM compilation and execution around verified bytecode, explicit call frames, protected regions, closures, containers, imports, and runtime callables.
  • Made GC marking iterative and tightened roots for pending raises, host-held values, exports, and cross-runtime payloads.
  • Added recursion/depth limits where script-shaped values could otherwise overflow the Rust stack.
  • Unified behavior that previously drifted across equality, display, map-key conversion, type naming, slices, and container operations.
  • Preserved first-class raised values across function, module, task, and heap boundaries.

Native backend

  • Expanded the typed MIR and Cranelift pipeline across control flow, containers, dynamic values, closures, traits, globals, imports, stdlib calls, and machine-width operations.
  • Added native try/catch through outlined protected regions and the setjmp trampoline.
  • Hardened Tier 1 hybrid ownership and reachability so outlined try bodies cannot become invalid VM bridge targets or dangling MIR calls.
  • Added MIR validation for function references, protected calls, arity, and entry-return capability.
  • Added scope-drop, CSE, DCE, ABI-schema conformance, optimized-build coverage, and native container-result display.
  • Kept unsupported shapes explicit: eligible helpers use the hybrid bridge, while other valid programs use the Tier 0 VM bundle.

Standard library and platforms

  • Expanded and aligned the desktop standard library across IO, encoding, bytes, iterators, math, strings, filesystem, process, environment, regex, random, networking, streams, tasks, channels, and time.
  • Kept browser and bare-metal module surfaces explicit, with computation-only modules available under no_std.
  • Added and extended Cortex-M VM execution, AArch64 native object execution, and the x86-64 bare-metal kernel, drivers, tasks, user mode, storage, input, and display checks.

Tooling and ecosystem

  • Expanded the CLI across checking, formatting, bytecode/native/object compilation, bundling, coverage, macro inspection, and package workflows.
  • Extended LSP diagnostics, completion, hover, definitions, references, rename, semantic tokens, code actions, code lenses, formatting, and inlay hints.
  • Updated VS Code, Zed, tree-sitter, WASM playground, examples, documentation, benchmarks, and CI scripts.

Verification

Verified locally on the current head:

  • cargo test --workspace --all-features
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo clippy -p lk-core --no-default-features --all-targets -- -D warnings
  • cargo clippy -p lkrt --no-default-features --all-targets -- -D warnings
  • cargo test -p lk-core --no-default-features: 1113 passed, 1 ignored
  • AOT native-lowering coverage: 76/76, empty allowlist
  • VM/native repository sweep: 78 identical, 1 allowed timing divergence, 0 fallback
  • Strict AOT differential suites: 13/13 and 101/101
  • Hybrid compile suite: 7/7
  • Optimized AOT coverage and native execution checks
  • LSP latency budgets and compiler scaling budget
  • WASM, Zed, MCU, and no_std build checks
  • Rust and LK formatting checks

The GitHub Rust Check and Performance Gate for the current head are running.

@coderabbitai

ghost commented Jul 29, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 502 files, which is 352 over the limit of 150.

To get a review, reduce the PR to 150 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to Pro+ to raise the limit.

Usage-priced reviews support at most 300 files.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0d90441a-3573-4f6d-8b01-9e3429f1ac81

📥 Commits

Reviewing files that changed from the base of the PR and between 6e646ac and 692cb10.

⛔ Files ignored due to path filters (4)
  • Cargo.lock is excluded by !**/*.lock
  • bare-metal-native/Cargo.lock is excluded by !**/*.lock
  • bare-metal-x86/Cargo.lock is excluded by !**/*.lock
  • bare-metal/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (502)
  • .github/workflows/check.yml
  • .github/workflows/correctness.yml
  • .gitignore
  • .vscode/settings.json
  • Cargo.toml
  • Makefile
  • README.md
  • README.zh-CN.md
  • aot/abi/src/lib.rs
  • aot/codegen/src/clif.rs
  • aot/driver/src/native_executable.rs
  • aot/lower/src/capture.rs
  • aot/lower/src/cfg.rs
  • aot/lower/src/convert.rs
  • aot/lower/src/dyn_box.rs
  • aot/lower/src/function.rs
  • aot/lower/src/imports.rs
  • aot/lower/src/inst/call.rs
  • aot/lower/src/inst/container.rs
  • aot/lower/src/inst/control.rs
  • aot/lower/src/inst/global.rs
  • aot/lower/src/inst/mod.rs
  • aot/lower/src/inst/scalar.rs
  • aot/lower/src/inst/string.rs
  • aot/lower/src/lib.rs
  • aot/lower/src/lower_builtin.rs
  • aot/lower/src/lower_call.rs
  • aot/lower/src/lower_method.rs
  • aot/lower/src/lower_module.rs
  • aot/lower/src/prescan.rs
  • aot/lower/src/sig.rs
  • aot/lower/src/ssa.rs
  • aot/lower/src/tables.rs
  • aot/lower/src/tests.rs
  • aot/lower/src/trait_env.rs
  • aot/lower/src/try_region.rs
  • aot/lower/src/unsupported.rs
  • aot/lower/src/vocab.rs
  • aot/lower/tests/abi_names.rs
  • aot/lower/tests/hybrid_lowering.rs
  • aot/lower/tests/mir_snapshots.rs
  • aot/mir/src/lib.rs
  • aot/mir/src/opt.rs
  • aot/mir/src/opt/tests.rs
  • api-cabi/Cargo.toml
  • api-cabi/src/lib.rs
  • api/Cargo.toml
  • api/include/lk.h
  • api/src/lib.rs
  • bare-metal-native/program.lk
  • bare-metal-x86/Cargo.toml
  • bare-metal-x86/README.md
  • bare-metal-x86/build.rs
  • bare-metal-x86/check_clock.py
  • bare-metal-x86/check_disk.py
  • bare-metal-x86/check_drag.py
  • bare-metal-x86/check_exit.py
  • bare-metal-x86/check_focus.py
  • bare-metal-x86/check_hpet.py
  • bare-metal-x86/check_interpreted_driver.py
  • bare-metal-x86/check_mouse.py
  • bare-metal-x86/check_net.py
  • bare-metal-x86/check_pci.py
  • bare-metal-x86/check_run.py
  • bare-metal-x86/check_screen.py
  • bare-metal-x86/check_shell.py
  • bare-metal-x86/check_spawn.py
  • bare-metal-x86/check_stack.py
  • bare-metal-x86/check_tasks.py
  • bare-metal-x86/check_user.py
  • bare-metal-x86/drivers/arp.lk
  • bare-metal-x86/drivers/e1000.lk
  • bare-metal-x86/drivers/edu.lk
  • bare-metal-x86/drivers/framebuffer.lk
  • bare-metal-x86/drivers/gdt.lk
  • bare-metal-x86/drivers/heap.lk
  • bare-metal-x86/drivers/hpet.lk
  • bare-metal-x86/drivers/idt.lk
  • bare-metal-x86/drivers/keyboard.lk
  • bare-metal-x86/drivers/layout.lk
  • bare-metal-x86/drivers/pages.lk
  • bare-metal-x86/drivers/paging.lk
  • bare-metal-x86/drivers/pci.lk
  • bare-metal-x86/drivers/pic.lk
  • bare-metal-x86/drivers/rtc.lk
  • bare-metal-x86/drivers/serial.lk
  • bare-metal-x86/drivers/tarfs.lk
  • bare-metal-x86/drivers/tasks.lk
  • bare-metal-x86/drivers/tss.lk
  • bare-metal-x86/drivers/window.lk
  • bare-metal-x86/kernel.py
  • bare-metal-x86/link.ld
  • bare-metal-x86/program.lk
  • bare-metal-x86/run.sh
  • bare-metal-x86/src/boot.rs
  • bare-metal-x86/src/interrupts.rs
  • bare-metal-x86/src/main.rs
  • bare-metal-x86/src/tasks.rs
  • bare-metal-x86/src/user.rs
  • bare-metal-x86/src/user_programs.rs
  • bare-metal/Cargo.toml
  • bare-metal/uart.lk
  • bench/README.md
  • bench/run_workload_bench.sh
  • cli/Cargo.toml
  • cli/src/coverage.rs
  • cli/src/main.rs
  • cli/src/main_test.rs
  • cli/src/native_compile.rs
  • cli/src/paths.rs
  • cli/src/pkg.rs
  • cli/src/repl.rs
  • cli/src/repl_completion.rs
  • cli/tests/aot_differential_test.rs
  • cli/tests/aot_fuzz_differential_test.rs
  • cli/tests/boxed_receiver_coverage_test.rs
  • cli/tests/broken_pipe_test.rs
  • cli/tests/builtin_method_native_coverage_test.rs
  • cli/tests/bundle_container_parameter_test.rs
  • cli/tests/bundle_default_output_test.rs
  • cli/tests/bundle_derived_const_test.rs
  • cli/tests/bundle_function_count_test.rs
  • cli/tests/check_oracle_test.rs
  • cli/tests/clif_differential_test.rs
  • cli/tests/compile_cli_test.rs
  • cli/tests/construct_native_coverage_test.rs
  • cli/tests/cross_module_function_value_test.rs
  • cli/tests/hybrid_compile_test.rs
  • cli/tests/impl_on_builtin_test.rs
  • cli/tests/imported_type_construction_test.rs
  • cli/tests/proc_macro_dependency_cli_test.rs
  • cli/tests/repl_echo_test.rs
  • cli/tests/stdlib_module_native_coverage_test.rs
  • cli/tests/stdlib_named_params_test.rs
  • cli/tests/stdlib_surface_test.rs
  • cli/tests/stream_boundary_test.rs
  • cli/tests/tutorial_examples_test.rs
  • cli/tests/type_system_cli_test.rs
  • completion/Cargo.toml
  • completion/src/lib.rs
  • core/Cargo.toml
  • core/src/ast.rs
  • core/src/ast/ast_test.rs
  • core/src/ast/parser.rs
  • core/src/ast/parser/literals.rs
  • core/src/ast/parser/patterns.rs
  • core/src/ast/parser/support.rs
  • core/src/compat.rs
  • core/src/expr/expr_impl.rs
  • core/src/expr/expr_test.rs
  • core/src/expr/select_guard_parsing_test.rs
  • core/src/fmt.rs
  • core/src/fmt/fmt_test.rs
  • core/src/lib.rs
  • core/src/macro_system.rs
  • core/src/macro_system/expansion.rs
  • core/src/macro_system/hygiene_tests.rs
  • core/src/macro_system/hygiene_tests/internal_rules.rs
  • core/src/macro_system/imports.rs
  • core/src/macro_system/proc_deps.rs
  • core/src/macro_system/procedural/derive.rs
  • core/src/macro_system/procedural/origins.rs
  • core/src/macro_system/procedural/origins/tests.rs
  • core/src/macro_system/template_tests.rs
  • core/src/macro_system/validation_tests.rs
  • core/src/module.rs
  • core/src/operator/operator_test.rs
  • core/src/operator/syntax.rs
  • core/src/package.rs
  • core/src/resolve/slots.rs
  • core/src/rt.rs
  • core/src/rt/runtime.rs
  • core/src/stmt.rs
  • core/src/stmt/defer.rs
  • core/src/stmt/destructuring_test.rs
  • core/src/stmt/function_test.rs
  • core/src/stmt/init_order.rs
  • core/src/stmt/stmt_impl/ast.rs
  • core/src/stmt/stmt_impl/display.rs
  • core/src/stmt/stmt_impl/flow.rs
  • core/src/stmt/stmt_impl/mod.rs
  • core/src/stmt/stmt_impl/type_check.rs
  • core/src/stmt/stmt_parser/bindings.rs
  • core/src/stmt/stmt_parser/blocks.rs
  • core/src/stmt/stmt_parser/control.rs
  • core/src/stmt/stmt_parser/declarations.rs
  • core/src/stmt/stmt_parser/function.rs
  • core/src/stmt/stmt_parser/helpers.rs
  • core/src/stmt/stmt_parser/mod.rs
  • core/src/stmt/stmt_parser/program.rs
  • core/src/stmt/stmt_test.rs
  • core/src/stmt/struct_ctors.rs
  • core/src/stmt/trait_defaults.rs
  • core/src/syntax.rs
  • core/src/token.rs
  • core/src/token/error.rs
  • core/src/token/lexer.rs
  • core/src/token/token_test.rs
  • core/src/typ.rs
  • core/src/typ/builtin_method_sig.rs
  • core/src/typ/declared_signature.rs
  • core/src/typ/imports.rs
  • core/src/typ/observation_test.rs
  • core/src/typ/stdlib_sig.rs
  • core/src/typ/stdlib_sig_test.rs
  • core/src/typ/type_checker.rs
  • core/src/typ/type_checker/expressions.rs
  • core/src/typ/type_checker/expressions/calls.rs
  • core/src/typ/type_checker/expressions/literals.rs
  • core/src/typ/type_checker/expressions/stdlib.rs
  • core/src/typ/type_checker/patterns.rs
  • core/src/typ/type_checker/tests.rs
  • core/src/typ/type_checker_test.rs
  • core/src/typ/type_system.rs
  • core/src/typ/type_system_test.rs
  • core/src/type_syntax.rs
  • core/src/util.rs
  • core/src/util/text.rs
  • core/src/util/value_map.rs
  • core/src/val.rs
  • core/src/val/de.rs
  • core/src/val/position.rs
  • core/src/val/runtime_model.rs
  • core/src/val/runtime_model/equality.rs
  • core/src/val/runtime_model/heap.rs
  • core/src/val/ser.rs
  • core/src/val/type_info.rs
  • core/src/val/val_test.rs
  • core/src/vm.rs
  • core/src/vm/alloc.rs
  • core/src/vm/analysis.rs
  • core/src/vm/artifact.rs
  • core/src/vm/cache.rs
  • core/src/vm/compiler.rs
  • core/src/vm/compiler/assign.rs
  • core/src/vm/compiler/builder.rs
  • core/src/vm/compiler/call.rs
  • core/src/vm/compiler/const_maps.rs
  • core/src/vm/compiler/container_lower.rs
  • core/src/vm/compiler/control_flow.rs
  • core/src/vm/compiler/decls.rs
  • core/src/vm/compiler/entry.rs
  • core/src/vm/compiler/expr_lower.rs
  • core/src/vm/compiler/facts.rs
  • core/src/vm/compiler/facts_tests.rs
  • core/src/vm/compiler/for_value_usage.rs
  • core/src/vm/compiler/free_vars.rs
  • core/src/vm/compiler/inline.rs
  • core/src/vm/compiler/loop_consts.rs
  • core/src/vm/compiler/lower_into.rs
  • core/src/vm/compiler/match_expr.rs
  • core/src/vm/compiler/pattern_bind.rs
  • core/src/vm/compiler/pattern_control.rs
  • core/src/vm/compiler/range_loop.rs
  • core/src/vm/compiler/stmt_lower.rs
  • core/src/vm/compiler/support.rs
  • core/src/vm/compiler/tests.rs
  • core/src/vm/compiler/tests/arithmetic.rs
  • core/src/vm/compiler/tests/call_intrinsics.rs
  • core/src/vm/compiler/tests/loops.rs
  • core/src/vm/compiler/tests/misc.rs
  • core/src/vm/compiler/tests/template.rs
  • core/src/vm/context.rs
  • core/src/vm/context/core_methods.rs
  • core/src/vm/context/core_methods/bytes_dispatch.rs
  • core/src/vm/context/core_methods/list_dispatch.rs
  • core/src/vm/context/core_methods/slice_dispatch.rs
  • core/src/vm/exec.rs
  • core/src/vm/exec/arithmetic.rs
  • core/src/vm/exec/call.rs
  • core/src/vm/exec/callable_ops.rs
  • core/src/vm/exec/const_load.rs
  • core/src/vm/exec/container.rs
  • core/src/vm/exec/container/index.rs
  • core/src/vm/exec/container/set_index.rs
  • core/src/vm/exec/dispatch.rs
  • core/src/vm/exec/display.rs
  • core/src/vm/exec/exec_tests.rs
  • core/src/vm/exec/exec_tests/attributes.rs
  • core/src/vm/exec/exec_tests/basic.rs
  • core/src/vm/exec/exec_tests/calls.rs
  • core/src/vm/exec/exec_tests/container.rs
  • core/src/vm/exec/exec_tests/cross_heap.rs
  • core/src/vm/exec/exec_tests/gc_cell_error.rs
  • core/src/vm/exec/exec_tests/native.rs
  • core/src/vm/exec/format.rs
  • core/src/vm/exec/frame.rs
  • core/src/vm/exec/gc.rs
  • core/src/vm/exec/handler.rs
  • core/src/vm/exec/imports.rs
  • core/src/vm/exec/named_call.rs
  • core/src/vm/exec/program.rs
  • core/src/vm/exec/result.rs
  • core/src/vm/exec/runners.rs
  • core/src/vm/exec/runtime_callable.rs
  • core/src/vm/exec/stack.rs
  • core/src/vm/exec/support.rs
  • core/src/vm/exec/value_ops.rs
  • core/src/vm/gc.rs
  • core/src/vm/hardware.rs
  • core/src/vm/ir.rs
  • core/src/vm/migration_guard.rs
  • core/src/vm/repl.rs
  • core/src/vm/resolver.rs
  • core/src/vm/runtime.rs
  • core/src/vm/ssa.rs
  • core/src/vm/ssa/escape.rs
  • core/src/vm/ssa/pipeline.rs
  • core/src/vm/type_info.rs
  • core/src/vm/verify.rs
  • docs/aot/aot-gaps-and-lkrt.md
  • docs/aot/native-stdlib.md
  • docs/aot/tier1-hybrid.md
  • docs/concurrency.md
  • docs/macros.md
  • docs/module-cycles.md
  • docs/packages.md
  • docs/semantics.md
  • docs/stdlib.md
  • docs/testing.md
  • docs/vm-cross-module-dispatch.md
  • ecosystem/tree-sitter-lk/grammar.js
  • ecosystem/tree-sitter-lk/src/grammar.json
  • ecosystem/tree-sitter-lk/src/node-types.json
  • ecosystem/tree-sitter-lk/src/parser.c
  • ecosystem/vsc-ext/lsp/README.md
  • ecosystem/vsc-ext/lsp/package.json
  • ecosystem/vsc-ext/lsp/src/extension.ts
  • ecosystem/vsc-ext/lsp/syntaxes/lk.tmLanguage.json
  • ecosystem/zed-ext/src/lib.rs
  • examples/general/concurrency_demo.lk
  • examples/general/config_parser.lk
  • examples/general/higher_order.lk
  • examples/general/point.lk
  • examples/general/raising.lk
  • examples/general/recursive.lk
  • examples/general/sort_search.lk
  • examples/general/word_count.lk
  • examples/stdlib/bytes_codec.lk
  • examples/stdlib/comprehensive.lk
  • examples/stdlib/datetime_demo.lk
  • examples/stdlib/json_process.lk
  • examples/stdlib/list_ops.lk
  • examples/stdlib/map_demo.lk
  • examples/stdlib/math_demo.lk
  • examples/stdlib/method_surface.lk
  • examples/stdlib/os_demo.lk
  • examples/stdlib/path_normalize.lk
  • examples/stdlib/stream_identity.lk
  • examples/stdlib/string_methods.lk
  • examples/stdlib/time_demo.lk
  • examples/stdlib/yaml_toml.lk
  • examples/syntax/closure.lk
  • examples/syntax/closure_value.lk
  • examples/syntax/control_flow.lk
  • examples/syntax/cross_module_raise.lk
  • examples/syntax/cross_task_raise.lk
  • examples/syntax/defer.lk
  • examples/syntax/error_handling.lk
  • examples/syntax/error_model_edges.lk
  • examples/syntax/error_unwrap.lk
  • examples/syntax/for_loop_patterns.lk
  • examples/syntax/handle_identity.lk
  • examples/syntax/impl_builtin.lk
  • examples/syntax/impl_inherent.lk
  • examples/syntax/index_by_variable.lk
  • examples/syntax/internal.lk
  • examples/syntax/macro_internal_rules.lk
  • examples/syntax/macros.lk
  • examples/syntax/map_order.lk
  • examples/syntax/match.lk
  • examples/syntax/named_default_scope.lk
  • examples/syntax/nested_assignment.lk
  • examples/syntax/null_coalescing.lk
  • examples/syntax/numeric_auto_promotion.lk
  • examples/syntax/operators.lk
  • examples/syntax/pattern_matching.lk
  • examples/syntax/raise_interrupt.lk
  • examples/syntax/ranges.lk
  • examples/syntax/select.lk
  • examples/syntax/shadowing.lk
  • examples/syntax/struct.lk
  • examples/syntax/struct_trait.lk
  • examples/syntax/template_infer.lk
  • examples/syntax/template_strings.lk
  • examples/syntax/trait_as_type.lk
  • examples/syntax/trait_builtin.lk
  • examples/syntax/try_catch.lk
  • examples/syntax/try_expression.lk
  • examples/syntax/unsupported.lk
  • examples/syntax/use.lk
  • examples/syntax/use_forms.lk
  • lkrt/Cargo.toml
  • lkrt/build.rs
  • lkrt/src/abi.rs
  • lkrt/src/abi_conformance_test.rs
  • lkrt/src/arith.rs
  • lkrt/src/chan.rs
  • lkrt/src/encoding.rs
  • lkrt/src/hash.rs
  • lkrt/src/host.rs
  • lkrt/src/io.rs
  • lkrt/src/io_bare.rs
  • lkrt/src/isr.rs
  • lkrt/src/lib.rs
  • lkrt/src/lkbytes.rs
  • lkrt/src/lkclosure.rs
  • lkrt/src/lkdyn.rs
  • lkrt/src/lklist.rs
  • lkrt/src/lkmap.rs
  • lkrt/src/lkprocess.rs
  • lkrt/src/lkrandom.rs
  • lkrt/src/lkregex.rs
  • lkrt/src/lkset.rs
  • lkrt/src/lkslice.rs
  • lkrt/src/lkstr.rs
  • lkrt/src/mmio.rs
  • lkrt/src/net.rs
  • lkrt/src/panic.rs
  • lkrt/src/stack_guard.c
  • lkrt/src/state.rs
  • lkrt/src/system.rs
  • lkrt/src/textcodec.rs
  • lkrt/src/try_trampoline.c
  • lkrt/src/uuid.rs
  • lkrt/src/vm_mirror.rs
  • lsp/Cargo.toml
  • lsp/src/analyzer/analysis_impl.rs
  • lsp/src/analyzer/core_impl.rs
  • lsp/src/analyzer/mod.rs
  • lsp/src/analyzer/tests.rs
  • lsp/src/bench_test.rs
  • lsp/src/editor_grammar_test.rs
  • lsp/src/inlay_hint_test.rs
  • lsp/src/lib.rs
  • lsp/src/main.rs
  • lsp/src/server/analysis.rs
  • lsp/src/server/completion.rs
  • lsp/src/server/handlers.rs
  • lsp/src/server/hover.rs
  • lsp/src/server/workspace_cache.rs
  • lsp/tests/integration_test.rs
  • lsp/tests/perf_latency_test.rs
  • lsp/tests/stdlib_completion_test.rs
  • lsp/tests/type_diagnostic_test.rs
  • scripts/aot_coverage.sh
  • scripts/build_lkrt_asan.sh
  • scripts/debug-vscode-lsp.sh
  • scripts/install_vsix.sh
  • scripts/install_zed_ext.sh
  • scripts/lib/vscode_cli.sh
  • scripts/prune_target.sh
  • scripts/verify.sh
  • scripts/vm_native_sweep.sh
  • stdlib/Cargo.toml
  • stdlib/bare/Cargo.toml
  • stdlib/bare/src/lib.rs
  • stdlib/common/src/language.rs
  • stdlib/common/src/lib.rs
  • stdlib/common/src/metadata.rs
  • stdlib/common/src/runtime_native.rs
  • stdlib/crates/bytes/src/lib.rs
  • stdlib/crates/chan/src/lib.rs
  • stdlib/crates/datetime/src/lib.rs
  • stdlib/crates/encoding/src/lib.rs
  • stdlib/crates/env/src/lib.rs
  • stdlib/crates/fs/src/lib.rs
  • stdlib/crates/http/src/lib.rs
  • stdlib/crates/iter/src/lib.rs
  • stdlib/crates/math/src/lib.rs
  • stdlib/crates/math/src/seed.rs
  • stdlib/crates/net/src/udp.rs
  • stdlib/crates/path/src/lib.rs
  • stdlib/crates/process/src/lib.rs
  • stdlib/crates/random/src/lib.rs
  • stdlib/crates/regex/src/lib.rs
  • stdlib/crates/slice/Cargo.toml
  • stdlib/crates/slice/src/lib.rs
  • stdlib/crates/stream/src/lib.rs
  • stdlib/crates/string/src/lib.rs
  • stdlib/crates/task/src/lib.rs
  • stdlib/crates/time/src/lib.rs
  • stdlib/macros/src/lib.rs
  • stdlib/src/bytes_test.rs
  • stdlib/src/chan_semantics_test.rs
  • stdlib/src/datetime_test.rs
  • stdlib/src/globals_test.rs
  • stdlib/src/host_parity_test.rs
  • stdlib/src/lib.rs
  • stdlib/src/math_test.rs
  • stdlib/src/platform_surface_test.rs
  • stdlib/src/stdlib_modules_test.rs
  • stdlib/src/stdlib_runtime_test.rs
  • stdlib/src/string_test.rs
  • stdlib/web/Cargo.toml
  • stdlib/web/src/lib.rs
  • values/src/lib.rs
  • values/src/types.rs
  • website/src/learn/LEARN.md
  • website/src/learn/LEARN_zh.md
  • website/src/stdlib/STDLIB.md
  • website/src/stdlib/STDLIB_zh.md

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


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

@cloudflare-workers-and-pages

ghost commented Jul 29, 2026

Copy link
Copy Markdown

Deploying lk-lang with  Cloudflare Pages  Cloudflare Pages

Latest commit: 692cb10
Status: ✅  Deploy successful!
Preview URL: https://4895cf12.lk-d8q.pages.dev
Branch Preview URL: https://feat-aot-try-catch.lk-d8q.pages.dev

View logs

@winnowl

ghost commented Jul 29, 2026

Copy link
Copy Markdown

CI failure root-cause analysis

Job 97044463669 did not fail because of a reported test assertion: every emitted test result is ok with zero failures. The CI wrapper nevertheless accumulated fail=1, indicating that one image/integration check failed or timed out. The available diagnostics do not identify which image, whether it timed out, or the underlying fault; the exception-reporter checks shown are wrapper assertions and are not themselves evidence of the fault. Root cause is therefore limited to an unlocalized image-level CI failure, not attributable to a source change from this data.

Verifiable fix

Preserve and expose the per-image command, exit status, timeout status, and complete semihosting output before aggregating into fail; rerun the identified image directly. If it timed out, fix the image/runtime hang or adjust the validated timeout; if the exception-reporter assertions failed, fix the reporter/output contract. A successful rerun must show the previously failing image completing and all wrapper assertions passing.

Incremental value: root cause, verifiable fix; confidence 98%. Passing CI ≠ absence of defects (§29.4).

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🔎 Confirmed findings (4)
  • medium A defer at program scope is silently accepted and rewritten instead of being rejected as invalid placement. desugar_defers calls rewrite_sequence(statements) on the program's top-level statement vector, and rewrite_sequence treats every Stmt::Defer there as pending, removes it, and appends its body at program fall-off. Thus source such as defer f(); becomes a top-level f(); rather than producing the required placement diagnostic. (inline)
  • medium The nested-definition round-limit error loses both the originating source span and the macro expansion stack. A macro that emits a macro_rules! definition which emits another definition for more than eight rounds reaches the explicit ParseError::new(...) branch and returns only a global message; unlike rule mismatches and recursive invocation failures, it has no with_span/origin information. This violates the error-handling obligation for recursion/nesting errors and makes diagnostics point nowhere even though every generated definition has an origin frame. The claim would be false only if callers intentionally require this specific cap error to be locationless, or if another layer attaches the generated definition's origin after expand_macros returns (the shown function returns the error directly). (inline)
  • medium The new API C-ABI packaging path is not usable on Windows because ensure_lk_api_staticlib always looks for the Unix archive name liblk_api_cabi.a; the Windows static-library output is lk_api_cabi.lib, so a successful cargo build -p lk-api-cabi --release is followed by linking a nonexistent file. (inline)
  • medium build_user_space leaks every page allocated before a partial allocation failure, leaving the page allocator's used count and free memory permanently consumed. (inline)
🧹 Additional findings from this change (not shown inline) (13)
  • [medium] Deferred return values can collide with an ordinary user variable because the rewrite parks every return expression in the fixed, user-spellable name __lk_defer_return. For example, a function containing let __lk_defer_return = 7; defer cleanup(); return __lk_defer_return; receives another generated let __lk_defer_return = ... before the cleanup and then returns that slot. Depending on resolver duplicate-binding rules this is either rejected unexpectedly or shadows/overwrites the user's binding, violating return-value preservation and the stated non-collision invariant.
  • [high] A module/package import is incorrectly treated as compile-time-only when its namespace contains any exported macro, so the runtime part of the same use is silently removed. For example, a file lib.lk containing fn value() { return 42; } and export macro_rules! helper { () => { 1 }; }, followed by use "lib"; return lib::value();, causes collect_imported_macro_defs to register lib::helper; then compile_time_macro_import_end_at sees registry.contains_macro_namespace("lib") and skips the entire use statement. The runtime resolver therefore never imports lib, and the otherwise valid lib::value() fails to resolve. The same applies to package namespace imports (use util;). This is introduced by the new namespace-based macro import collection/removal path; it would be false only if the language specification explicitly forbids a module that exports macros from also being runtime-imported by the same namespace use (or if the syntax layer separately preserves/reinserts the runtime import, which the shown removal path does not).
  • [high] The immediate modulo branch opcodes can still abort the host on i64::MIN % -1 instead of producing the VM's wrapping result or a catchable error. BrModEqZeroIntI4 and BrModNeZeroIntI4 evaluate *value % divisor directly, and Rust's signed remainder panics for i64::MIN % -1; the compiler can emit these optimized branches for modulo-based control flow, so a valid LK program reaching that value terminates the process rather than following language arithmetic/error semantics. This is introduced by the new opcode execution path (the ordinary ModInt paths were explicitly changed to wrapping_rem). It would be disproved if these opcodes were unreachable with divisor -1 or if all such execution were guarded/converting the operation before Rust evaluates %.
  • [medium] The bytecode verifier accepts TryEnd instructions without proving that a handler is active, and execution implements TryEnd as an unconditional handler_stack.pop() that silently removes the most recent unrelated handler. A malformed or control-flow-reachable artifact can therefore put TryEnd outside its protected region, erase an outer handler, and make a later raise escape (or be caught by the wrong region), while verify_module reports the artifact valid. The claim would be false if verifier control-flow validation elsewhere rejects every TryEnd not dominated by a matching TryBegin and preserves proper nesting; no such structural check is present in the instruction/fact verification shown.
  • [high] The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.
  • [high] The ABI classifies socket.addr as Pure, but its implementation allocates and registers a returned C string in the runtime arena via owned_c_string; this is an observable host/runtime write and must not be CSE'd, hoisted, or DCE'd as a pure call.
  • [high] The native stack-exhaustion guard is never installed, so runaway recursion still reaches the default SIGSEGV/SIGBUS path with no diagnostic or VM-compatible exit.
  • [medium] task.try_await never observes completion for real spawned tasks: it only reads the immutable TaskValue.value snapshot, which spawn initializes to None and never updates after the async task resolves.
  • [medium] The initial contiguous heap reservation does not roll back pages when it cannot obtain the full 16-page run, so a partially successful reservation permanently consumes RAM while installing an empty heap.
  • [low] Task deadlines do not handle the 32-bit wrap of the shared tick counter, so a task that sleeps across the wrap can remain blocked for roughly another full counter period.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
  • [info] Dependency memmap2@0.2.3 is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate memmap2); upgrade to at least 0.9.11.
  • [info] Dependency bare-metal@0.2.5 is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.
📚 Preexisting issues (unrelated to this change) (24)
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.
  • [high] Dependency form-data@4.0.5 is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.
  • [high] Dependency js-yaml@4.1.1 is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the mailto: validator scan-loop on attacker text); upgrade to at least 5.0.2.
  • [high] Dependency tmp@0.2.5 is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.
  • [medium] Dependency js-yaml@4.1.1 is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.
  • [medium] Dependency markdown-it@14.1.1 is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.
  • [medium] Dependency qs@6.15.1 is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
🗑️ Suppressed and duplicate diagnostics (1)
  • The API static-library resolver hard-codes workspace/target/release/liblk_api_cabi.a after invoking Cargo, so builds using CARGO_TARGET_DIR (or a non-default target directory) successfully emit the archive elsewhere and then pass a nonexistent path to the linker.
🤖 Prompt for AI agents — all findings (41)
In core/src/stmt/defer.rs around line 104, address this finding:
A `defer` at program scope is silently accepted and rewritten instead of being rejected as invalid placement. `desugar_defers` calls `rewrite_sequence(statements)` on the program's top-level statement vector, and `rewrite_sequence` treats every `Stmt::Defer` there as pending, removes it, and appends its body at program fall-off. Thus source such as `defer f();` becomes a top-level `f();` rather than producing the required placement diagnostic.

In core/src/stmt/defer.rs around line 101, address this finding:
Deferred return values can collide with an ordinary user variable because the rewrite parks every return expression in the fixed, user-spellable name `__lk_defer_return`. For example, a function containing `let __lk_defer_return = 7; defer cleanup(); return __lk_defer_return;` receives another generated `let __lk_defer_return = ...` before the cleanup and then returns that slot. Depending on resolver duplicate-binding rules this is either rejected unexpectedly or shadows/overwrites the user's binding, violating return-value preservation and the stated non-collision invariant.

In core/src/macro_system/imports.rs around line 187, address this finding:
A module/package import is incorrectly treated as compile-time-only when its namespace contains any exported macro, so the runtime part of the same `use` is silently removed. For example, a file `lib.lk` containing `fn value() { return 42; }` and `export macro_rules! helper { () => { 1 }; }`, followed by `use "lib"; return lib::value();`, causes `collect_imported_macro_defs` to register `lib::helper`; then `compile_time_macro_import_end_at` sees `registry.contains_macro_namespace("lib")` and skips the entire `use` statement. The runtime resolver therefore never imports `lib`, and the otherwise valid `lib::value()` fails to resolve. The same applies to package namespace imports (`use util;`). This is introduced by the new namespace-based macro import collection/removal path; it would be false only if the language specification explicitly forbids a module that exports macros from also being runtime-imported by the same namespace `use` (or if the syntax layer separately preserves/reinserts the runtime import, which the shown removal path does not).

In core/src/macro_system.rs around line 332, address this finding:
The nested-definition round-limit error loses both the originating source span and the macro expansion stack. A macro that emits a `macro_rules!` definition which emits another definition for more than eight rounds reaches the explicit `ParseError::new(...)` branch and returns only a global message; unlike rule mismatches and recursive invocation failures, it has no `with_span`/origin information. This violates the error-handling obligation for recursion/nesting errors and makes diagnostics point nowhere even though every generated definition has an origin frame. The claim would be false only if callers intentionally require this specific cap error to be locationless, or if another layer attaches the generated definition's origin after `expand_macros` returns (the shown function returns the error directly).

In core/src/vm/exec.rs, address this finding:
The immediate modulo branch opcodes can still abort the host on `i64::MIN % -1` instead of producing the VM's wrapping result or a catchable error. `BrModEqZeroIntI4` and `BrModNeZeroIntI4` evaluate `*value % divisor` directly, and Rust's signed remainder panics for `i64::MIN % -1`; the compiler can emit these optimized branches for modulo-based control flow, so a valid LK program reaching that value terminates the process rather than following language arithmetic/error semantics. This is introduced by the new opcode execution path (the ordinary ModInt paths were explicitly changed to wrapping_rem). It would be disproved if these opcodes were unreachable with divisor -1 or if all such execution were guarded/converting the operation before Rust evaluates `%`.

In core/src/vm/exec/handler.rs around line 171, address this finding:
The bytecode verifier accepts `TryEnd` instructions without proving that a handler is active, and execution implements `TryEnd` as an unconditional `handler_stack.pop()` that silently removes the most recent unrelated handler. A malformed or control-flow-reachable artifact can therefore put `TryEnd` outside its protected region, erase an outer handler, and make a later raise escape (or be caught by the wrong region), while `verify_module` reports the artifact valid. The claim would be false if verifier control-flow validation elsewhere rejects every `TryEnd` not dominated by a matching `TryBegin` and preserves proper nesting; no such structural check is present in the instruction/fact verification shown.

In aot/codegen/src/clif.rs, address this finding:
The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.

In aot/abi/src/lib.rs around line 262, address this finding:
The ABI classifies `socket.addr` as `Pure`, but its implementation allocates and registers a returned C string in the runtime arena via `owned_c_string`; this is an observable host/runtime write and must not be CSE'd, hoisted, or DCE'd as a pure call.

In cli/src/native_compile.rs around line 26, address this finding:
The new API C-ABI packaging path is not usable on Windows because `ensure_lk_api_staticlib` always looks for the Unix archive name `liblk_api_cabi.a`; the Windows static-library output is `lk_api_cabi.lib`, so a successful `cargo build -p lk-api-cabi --release` is followed by linking a nonexistent file.

In lkrt/src/abi.rs around line 140, address this finding:
The native stack-exhaustion guard is never installed, so runaway recursion still reaches the default SIGSEGV/SIGBUS path with no diagnostic or VM-compatible exit.

In stdlib/crates/task/src/lib.rs around line 43, address this finding:
`task.try_await` never observes completion for real spawned tasks: it only reads the immutable `TaskValue.value` snapshot, which `spawn` initializes to `None` and never updates after the async task resolves.

In bare-metal-x86/program.lk around line 2811, address this finding:
`build_user_space` leaks every page allocated before a partial allocation failure, leaving the page allocator's `used` count and free memory permanently consumed.

In bare-metal-x86/program.lk around line 3870, address this finding:
The initial contiguous heap reservation does not roll back pages when it cannot obtain the full 16-page run, so a partially successful reservation permanently consumes RAM while installing an empty heap.

In bare-metal-x86/program.lk around line 2539, address this finding:
Task deadlines do not handle the 32-bit wrap of the shared tick counter, so a task that sleeps across the wrap can remain blocked for roughly another full counter period.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `form-data@4.0.5` is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the `mailto:` validator scan-loop on attacker text); upgrade to at least 5.0.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `tmp@0.2.5` is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `markdown-it@14.1.1` is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `qs@6.15.1` is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.

In Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.

In Cargo.lock, address this finding:
Dependency `memmap2@0.2.3` is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate `memmap2`); upgrade to at least 0.9.11.

In bare-metal/Cargo.lock, address this finding:
Dependency `bare-metal@0.2.5` is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.

In ecosystem/zed-ext/Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.
📜 Review details

Coverage

  • scopes: 6/10 complete

Comment thread core/src/stmt/defer.rs
const RETURN_SLOT: &str = "__lk_defer_return";

/// Rewrites a program so every `defer` runs on the way out of its function.
pub fn desugar_defers(statements: &mut Vec<Box<Stmt>>) -> Result<(), String> {

ghost Jul 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟡 Medium

A defer at program scope is silently accepted and rewritten instead of being rejected as invalid placement. desugar_defers calls rewrite_sequence(statements) on the program's top-level statement vector, and rewrite_sequence treats every Stmt::Defer there as pending, removes it, and appends its body at program fall-off. Thus source such as defer f(); becomes a top-level f(); rather than producing the required placement diagnostic.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In core/src/stmt/defer.rs, address this finding:
A `defer` at program scope is silently accepted and rewritten instead of being rejected as invalid placement. `desugar_defers` calls `rewrite_sequence(statements)` on the program's top-level statement vector, and `rewrite_sequence` treats every `Stmt::Defer` there as pending, removes it, and appends its body at program fall-off. Thus source such as `defer f();` becomes a top-level `f();` rather than producing the required placement diagnostic.

To have the bot fix this, comment @winnowl fix.

Comment thread core/src/macro_system.rs
}
rounds += 1;
if rounds >= MAX_DEFINITION_ROUNDS {
return Err(ParseError::new(alloc::format!(

ghost Jul 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Error Handling | 🟡 Medium

The nested-definition round-limit error loses both the originating source span and the macro expansion stack. A macro that emits a macro_rules! definition which emits another definition for more than eight rounds reaches the explicit ParseError::new(...) branch and returns only a global message; unlike rule mismatches and recursive invocation failures, it has no with_span/origin information. This violates the error-handling obligation for recursion/nesting errors and makes diagnostics point nowhere even though every generated definition has an origin frame. The claim would be false only if callers intentionally require this specific cap error to be locationless, or if another layer attaches the generated definition's origin after expand_macros returns (the shown function returns the error directly).

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In core/src/macro_system.rs, address this finding:
The nested-definition round-limit error loses both the originating source span and the macro expansion stack. A macro that emits a `macro_rules!` definition which emits another definition for more than eight rounds reaches the explicit `ParseError::new(...)` branch and returns only a global message; unlike rule mismatches and recursive invocation failures, it has no `with_span`/origin information. This violates the error-handling obligation for recursion/nesting errors and makes diagnostics point nowhere even though every generated definition has an origin frame. The claim would be false only if callers intentionally require this specific cap error to be locationless, or if another layer attaches the generated definition's origin after `expand_macros` returns (the shown function returns the error directly).

To have the bot fix this, comment @winnowl fix.

Comment thread cli/src/native_compile.rs
// that an ordinary `cargo build`/`cargo test` stops emitting 172MB of it
// for a linker path it never takes. See that crate's docs. The `ffi`
// feature now rides along in its manifest rather than on this command line.
let staticlib = workspace.join("target/release/liblk_api_cabi.a");

ghost Jul 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Build Deployment | 🟡 Medium

The new API C-ABI packaging path is not usable on Windows because ensure_lk_api_staticlib always looks for the Unix archive name liblk_api_cabi.a; the Windows static-library output is lk_api_cabi.lib, so a successful cargo build -p lk-api-cabi --release is followed by linking a nonexistent file.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ The defect is specific to Windows targets using the MSVC naming convention (for example x86_64-pc-windows-msvc); Windows GNU targets may emit a Unix-style .a archive.
  • ⚠️ The LK_API_STATICLIB environment-variable override can work around the defect, but the default packaging path remains broken.
🤖 Prompt for AI agents
In cli/src/native_compile.rs, address this finding:
The new API C-ABI packaging path is not usable on Windows because `ensure_lk_api_staticlib` always looks for the Unix archive name `liblk_api_cabi.a`; the Windows static-library output is `lk_api_cabi.lib`, so a successful `cargo build -p lk-api-cabi --release` is followed by linking a nonexistent file.

To have the bot fix this, comment @winnowl fix.

Comment thread bare-metal-x86/program.lk
let pdpt = page_alloc(SHARED_PAGES);
let directory = page_alloc(SHARED_PAGES);
let table = page_alloc(SHARED_PAGES);
if (pml4 == 0 || pdpt == 0 || directory == 0 || table == 0) {

ghost Jul 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Resource Lifetime | 🟡 Medium

build_user_space leaks every page allocated before a partial allocation failure, leaving the page allocator's used count and free memory permanently consumed.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ The normal boot configuration may usually have enough pages, but the allocator explicitly supports exhaustion and the user-space builder has no recovery path for partial allocation.
🤖 Prompt for AI agents
In bare-metal-x86/program.lk, address this finding:
`build_user_space` leaks every page allocated before a partial allocation failure, leaving the page allocator's `used` count and free memory permanently consumed.

To have the bot fix this, comment @winnowl fix.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🔎 Confirmed findings (3)
  • high Trait default methods containing defer are copied into impls after the defer pass but are never themselves rewritten, so the generated impl contains a surviving Stmt::Defer (and therefore either fails downstream or violates the guarantee that defer is erased). (inline)
  • high The stdlib-facing equality implementation was not actually consolidated onto the new runtime equality. stdlib/common/src/runtime_native.rs::runtime_values_equal still contains the old recursive implementation, so assertion and other stdlib callers can disagree with VM ==: it has no Slice/Bytes/Object structural arms, no depth guard, and its float/list behavior is a separate copy. (inline)
  • medium Reaping a dead user task releases only its kernel stack and permanently leaks the four physical pages allocated for its PML4, PDPT, directory, and page table. If a user task can exit and slots are reused, repeated spawn/exit cycles eventually exhaust the page allocator even though the task table reports slots free; the partial-allocation failure path in build_user_space also leaks any pages allocated before the first zero. The invariant is that all per-task address-space allocations remain reclaimable on every exit/fault/spawn-failure path; this change relates directly because it introduced dynamically allocated user address spaces and task reaping without adding corresponding page-table cleanup. This would be disproven only if user address spaces are provably never destroyed/reused or those four pages are reclaimed through another path, neither of which is present in the shown creation/reaping paths. (inline)

⛔ Unresolved from previous review (1) — not approved until fixed

  • The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.
🧹 Additional findings from this change (not shown inline) (10)
  • [medium] Slice property access reports the snapshot length instead of the live source-clamped length. After let s = xs.slice(...); xs.pop() (or equivalent shrinkage), s.len goes through the HeapValue::Slice arm and returns slice.len, while s.len() dispatches through slice_dispatch and returns slice.live_len(heap). The same slice therefore exposes two different lengths and can make bounds-dependent callers disagree.
  • [medium] Serializing a dangling slice silently changes its value into an empty JSON array instead of rejecting the invalid heap reference. In heap_value_to_serde, a Slice whose source is not an object/list returns [], including a collected source handle; this loses data and differs from ordinary dangling RuntimeVal::Obj handling, which reports heap object ... out of bounds. A slice retained across GC can therefore serialize successfully to a value it never represented.
  • [high] Structural equality can still overflow the Rust stack on deeply nested structs because the object-field recursion bypasses the depth guard. objects() calls self.values(left_value, right_value, depth + 1) directly; unlike nested(), values() does not check MAX_VALUE_DEPTH. Two chains of RuntimeObject values can therefore recurse past 512 (and sufficiently deep chains abort rather than returning the documented catchable comparison error), even though lists/maps are guarded.
  • [high] Native try regions containing a Float value read by the outlined body are rejected by the Cranelift backend instead of lowering with the documented bit-preserving float path. crosses_as_word accepts Ty::F64 and the body-side setup adds BitsToFloat, but the caller builds TryRegionCall.args with the original F64 SSA value; Lower::TryRegionCall only accepts CLIF I64/I8 words and returns Unsupported("try-call non-integer argument") for F64. Thus a function such as fn f(x: Float) { try { x + 1.0 } catch e { ... } } falls back (or fails native-only), violating supported float try arguments and the performance/native-lowering contract. This would be disproved if the MIR producer inserted an explicit F64-to-I64 bitcast before every TryRegionCall argument, but the shown caller passes v directly and emits no such conversion.
  • [high] return; inside an outlined try body does not propagate the enclosing-function return channel. The region scanner marks Opcode::Return0 as body_returns and the caller allocates/passes flag and value cells, but the body lowering only parks a return when matching Some(Exit::Ret(Some(reg))); Exit::Ret(None) bypasses that code and leaves the flag at its seeded false value. On the normal body-completion edge the caller therefore treats return; as a raised/catch outcome (or, for an all-return body, reaches the wrong continuation) instead of returning nil from the enclosing function. A minimal failure is try { return; } catch e { print("caught") } in a non-entry function: the VM returns immediately, while native lowering enters the catch path. This would be disproved if Return0 were converted to an Exit::Ret(Some(...)) before this match or if another lowering path sets the return flag for Ret(None), neither of which is present in the inspected code.
  • [medium] ensure_lk_api_staticlib does not honor Cargo's configured target directory, so a valid hybrid build fails to find the archive it just built when CARGO_TARGET_DIR (or a Cargo target-dir config) is set.
  • [medium] bytes.slice has inconsistent end-before-start semantics between the module export and the unified method surface: bytes.slice(b, 2, 1) raises, while b.slice(2, 1) silently returns an empty Bytes because the VM dispatch applies end.max(start). This violates the intended parity of the two spellings and makes diagnostics depend on call syntax. The regression is in the revised module implementation (the method behavior is visible in core/src/vm/context/core_methods/bytes_dispatch.rs), and it would be disproved if the language specification intentionally defines module calls to reject reversed bounds while methods clamp them (or if both paths are shown to be normalized before dispatch).
  • [high] The correctness workflow invokes an integration test target that does not exist in the workspace, so the sanitized differential job (and the mirrored Make targets) cannot complete successfully.
  • [high] The Tier-1 hybrid bridge uses one module-global lk_hybrid_argbuf for every CallVm site, but the native runtime also supports concurrent spawned tasks. If two native threads execute a bridged call concurrently, each overwrites the shared tags/payloads before the other bridge reads them, so the VM function can receive another thread's arguments (or inconsistent tag/value pairs). The comment's single-threaded assumption is not enforced by the language/runtime.
  • [medium] Hybrid native linking still combines lkrt-cabi and lk-api-cabi, whose archives contain duplicate transitive dependency objects (the code comments specifically identify unsafe_libyaml and hundreds of duplicate definitions). The only duplicate-definition suppression is --allow-multiple-definition, guarded out on macOS, so hybrid builds on macOS fail at link time (or otherwise expose duplicate symbols) even though the same path is supported on other Unix hosts.
♻️ Previously reported (still present) (27)
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.
  • [high] Dependency form-data@4.0.5 is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.
  • [high] Dependency js-yaml@4.1.1 is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the mailto: validator scan-loop on attacker text); upgrade to at least 5.0.2.
  • [high] Dependency tmp@0.2.5 is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.
  • [medium] Dependency js-yaml@4.1.1 is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.
  • [medium] Dependency markdown-it@14.1.1 is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.
  • [medium] Dependency qs@6.15.1 is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
  • [info] Dependency memmap2@0.2.3 is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate memmap2); upgrade to at least 0.9.11.
  • [info] Dependency bare-metal@0.2.5 is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
🤖 Prompt for AI agents — all findings (40)
In core/src/stmt/defer.rs around line 144, address this finding:
Trait default methods containing `defer` are copied into impls after the defer pass but are never themselves rewritten, so the generated impl contains a surviving `Stmt::Defer` (and therefore either fails downstream or violates the guarantee that defer is erased).

In stdlib/common/src/runtime_native.rs around line 141, address this finding:
The stdlib-facing equality implementation was not actually consolidated onto the new runtime equality. `stdlib/common/src/runtime_native.rs::runtime_values_equal` still contains the old recursive implementation, so assertion and other stdlib callers can disagree with VM `==`: it has no Slice/Bytes/Object structural arms, no depth guard, and its float/list behavior is a separate copy.

In core/src/vm/context/core_methods.rs, address this finding:
Slice property access reports the snapshot length instead of the live source-clamped length. After `let s = xs.slice(...); xs.pop()` (or equivalent shrinkage), `s.len` goes through the `HeapValue::Slice` arm and returns `slice.len`, while `s.len()` dispatches through `slice_dispatch` and returns `slice.live_len(heap)`. The same slice therefore exposes two different lengths and can make bounds-dependent callers disagree.

In core/src/val/ser.rs, address this finding:
Serializing a dangling slice silently changes its value into an empty JSON array instead of rejecting the invalid heap reference. In `heap_value_to_serde`, a Slice whose source is not an object/list returns `[]`, including a collected source handle; this loses data and differs from ordinary dangling `RuntimeVal::Obj` handling, which reports `heap object ... out of bounds`. A slice retained across GC can therefore serialize successfully to a value it never represented.

In core/src/val/runtime_model/equality.rs around line 163, address this finding:
Structural equality can still overflow the Rust stack on deeply nested structs because the object-field recursion bypasses the depth guard. `objects()` calls `self.values(left_value, right_value, depth + 1)` directly; unlike `nested()`, `values()` does not check `MAX_VALUE_DEPTH`. Two chains of `RuntimeObject` values can therefore recurse past 512 (and sufficiently deep chains abort rather than returning the documented catchable comparison error), even though lists/maps are guarded.

In aot/codegen/src/clif.rs around line 1265, address this finding:
Native try regions containing a Float value read by the outlined body are rejected by the Cranelift backend instead of lowering with the documented bit-preserving float path. `crosses_as_word` accepts `Ty::F64` and the body-side setup adds `BitsToFloat`, but the caller builds `TryRegionCall.args` with the original F64 SSA value; `Lower::TryRegionCall` only accepts CLIF I64/I8 words and returns `Unsupported("try-call non-integer argument")` for F64. Thus a function such as `fn f(x: Float) { try { x + 1.0 } catch e { ... } }` falls back (or fails native-only), violating supported float try arguments and the performance/native-lowering contract. This would be disproved if the MIR producer inserted an explicit F64-to-I64 bitcast before every TryRegionCall argument, but the shown caller passes `v` directly and emits no such conversion.

In aot/lower/src/function.rs around line 631, address this finding:
`return;` inside an outlined try body does not propagate the enclosing-function return channel. The region scanner marks `Opcode::Return0` as `body_returns` and the caller allocates/passes flag and value cells, but the body lowering only parks a return when matching `Some(Exit::Ret(Some(reg)))`; `Exit::Ret(None)` bypasses that code and leaves the flag at its seeded false value. On the normal body-completion edge the caller therefore treats `return;` as a raised/catch outcome (or, for an all-return body, reaches the wrong continuation) instead of returning nil from the enclosing function. A minimal failure is `try { return; } catch e { print("caught") }` in a non-entry function: the VM returns immediately, while native lowering enters the catch path. This would be disproved if `Return0` were converted to an `Exit::Ret(Some(...))` before this match or if another lowering path sets the return flag for `Ret(None)`, neither of which is present in the inspected code.

In cli/src/native_compile.rs around line 26, address this finding:
`ensure_lk_api_staticlib` does not honor Cargo's configured target directory, so a valid hybrid build fails to find the archive it just built when `CARGO_TARGET_DIR` (or a Cargo `target-dir` config) is set.

In stdlib/crates/bytes/src/lib.rs around line 127, address this finding:
`bytes.slice` has inconsistent end-before-start semantics between the module export and the unified method surface: `bytes.slice(b, 2, 1)` raises, while `b.slice(2, 1)` silently returns an empty Bytes because the VM dispatch applies `end.max(start)`. This violates the intended parity of the two spellings and makes diagnostics depend on call syntax. The regression is in the revised module implementation (the method behavior is visible in `core/src/vm/context/core_methods/bytes_dispatch.rs`), and it would be disproved if the language specification intentionally defines module calls to reject reversed bounds while methods clamp them (or if both paths are shown to be normalized before dispatch).

In bare-metal-x86/program.lk around line 2624, address this finding:
Reaping a dead user task releases only its kernel stack and permanently leaks the four physical pages allocated for its PML4, PDPT, directory, and page table. If a user task can exit and slots are reused, repeated spawn/exit cycles eventually exhaust the page allocator even though the task table reports slots free; the partial-allocation failure path in `build_user_space` also leaks any pages allocated before the first zero. The invariant is that all per-task address-space allocations remain reclaimable on every exit/fault/spawn-failure path; this change relates directly because it introduced dynamically allocated user address spaces and task reaping without adding corresponding page-table cleanup. This would be disproven only if user address spaces are provably never destroyed/reused or those four pages are reclaimed through another path, neither of which is present in the shown creation/reaping paths.

In .github/workflows/correctness.yml, address this finding:
The correctness workflow invokes an integration test target that does not exist in the workspace, so the sanitized differential job (and the mirrored Make targets) cannot complete successfully.

In aot/codegen/src/clif.rs, address this finding:
The Tier-1 hybrid bridge uses one module-global `lk_hybrid_argbuf` for every `CallVm` site, but the native runtime also supports concurrent spawned tasks. If two native threads execute a bridged call concurrently, each overwrites the shared tags/payloads before the other bridge reads them, so the VM function can receive another thread's arguments (or inconsistent tag/value pairs). The comment's single-threaded assumption is not enforced by the language/runtime.

In aot/driver/src/native_executable.rs, address this finding:
Hybrid native linking still combines `lkrt-cabi` and `lk-api-cabi`, whose archives contain duplicate transitive dependency objects (the code comments specifically identify `unsafe_libyaml` and hundreds of duplicate definitions). The only duplicate-definition suppression is `--allow-multiple-definition`, guarded out on macOS, so hybrid builds on macOS fail at link time (or otherwise expose duplicate symbols) even though the same path is supported on other Unix hosts.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `form-data@4.0.5` is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the `mailto:` validator scan-loop on attacker text); upgrade to at least 5.0.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `tmp@0.2.5` is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `markdown-it@14.1.1` is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `qs@6.15.1` is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.

In Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.

In Cargo.lock, address this finding:
Dependency `memmap2@0.2.3` is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate `memmap2`); upgrade to at least 0.9.11.

In bare-metal/Cargo.lock, address this finding:
Dependency `bare-metal@0.2.5` is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.

In ecosystem/zed-ext/Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.
📜 Review details

Coverage

  • scopes: 6/7 complete

Comment thread core/src/stmt/defer.rs
}
}
Stmt::While { body, .. } | Stmt::For { body, .. } => descend(body),
Stmt::Impl { methods, .. } => {

ghost Jul 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟠 High

Trait default methods containing defer are copied into impls after the defer pass but are never themselves rewritten, so the generated impl contains a surviving Stmt::Defer (and therefore either fails downstream or violates the guarantee that defer is erased).

🧩 Analysis
  • Change relation: unknown
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In core/src/stmt/defer.rs, address this finding:
Trait default methods containing `defer` are copied into impls after the defer pass but are never themselves rewritten, so the generated impl contains a surviving `Stmt::Defer` (and therefore either fails downstream or violates the guarantee that defer is erased).

To have the bot fix this, comment @winnowl fix.

/// held. `ShortStr` is not the bug: it is a small-string optimisation that made
/// half the cases accidentally right. Take it away and the derive is uniformly
/// wrong instead of intermittently.
pub fn runtime_values_equal(left: &RuntimeVal, right: &RuntimeVal, heap: &HeapStore) -> Result<bool> {

ghost Jul 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟠 High

The stdlib-facing equality implementation was not actually consolidated onto the new runtime equality. stdlib/common/src/runtime_native.rs::runtime_values_equal still contains the old recursive implementation, so assertion and other stdlib callers can disagree with VM ==: it has no Slice/Bytes/Object structural arms, no depth guard, and its float/list behavior is a separate copy.

🧩 Analysis
  • Change relation: unknown
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ I did not execute the host binaries, so the conclusion is based on the directly reachable registrations and a manual counterexample.
  • ⚠️ Exact baseline provenance of every old helper line is unavailable, but the current change clearly leaves two active implementations with different behavior.
🤖 Prompt for AI agents
In stdlib/common/src/runtime_native.rs, address this finding:
The stdlib-facing equality implementation was not actually consolidated onto the new runtime equality. `stdlib/common/src/runtime_native.rs::runtime_values_equal` still contains the old recursive implementation, so assertion and other stdlib callers can disagree with VM `==`: it has no Slice/Bytes/Object structural arms, no depth guard, and its float/list behavior is a separate copy.

To have the bot fix this, comment @winnowl fix.

Comment thread bare-metal-x86/program.lk
//
// That is why `DEAD` is a state rather than a step. There is no moment inside
// the exiting task where the release is safe.
fn reap_dead_tasks(current: Int, next: Int) {

ghost Jul 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Resource Lifetime | 🟡 Medium

Reaping a dead user task releases only its kernel stack and permanently leaks the four physical pages allocated for its PML4, PDPT, directory, and page table. If a user task can exit and slots are reused, repeated spawn/exit cycles eventually exhaust the page allocator even though the task table reports slots free; the partial-allocation failure path in build_user_space also leaks any pages allocated before the first zero. The invariant is that all per-task address-space allocations remain reclaimable on every exit/fault/spawn-failure path; this change relates directly because it introduced dynamically allocated user address spaces and task reaping without adding corresponding page-table cleanup. This would be disproven only if user address spaces are provably never destroyed/reused or those four pages are reclaimed through another path, neither of which is present in the shown creation/reaping paths.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ The currently included ring-3 task bodies loop forever, and SYS_EXIT only sets a shared flag, so the dead-user-task reaping branch is not exercised by the shipped user programs. However, the allocation-failure leak is directly reachable under a sufficiently constrained page arena, and the reaper has no alternate CR3/page-table cleanup path if a user slot does become DEAD.
🤖 Prompt for AI agents
In bare-metal-x86/program.lk, address this finding:
Reaping a dead user task releases only its kernel stack and permanently leaks the four physical pages allocated for its PML4, PDPT, directory, and page table. If a user task can exit and slots are reused, repeated spawn/exit cycles eventually exhaust the page allocator even though the task table reports slots free; the partial-allocation failure path in `build_user_space` also leaks any pages allocated before the first zero. The invariant is that all per-task address-space allocations remain reclaimable on every exit/fault/spawn-failure path; this change relates directly because it introduced dynamically allocated user address spaces and task reaping without adding corresponding page-table cleanup. This would be disproven only if user address spaces are provably never destroyed/reused or those four pages are reclaimed through another path, neither of which is present in the shown creation/reaping paths.

To have the bot fix this, comment @winnowl fix.

@lollipopkit

ghost commented Jul 30, 2026

Copy link
Copy Markdown
Owner Author

@winnowl ignore

@winnowl

ghost commented Jul 30, 2026

Copy link
Copy Markdown

Your judgment has been recorded; a one-off decision does not automatically become a long-term rule.

@lollipopkit

ghost commented Jul 30, 2026

Copy link
Copy Markdown
Owner Author

@winnowl pause

@winnowl

ghost commented Jul 30, 2026

Copy link
Copy Markdown

Received the "run-command" command; that capability is still being wired up.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔎 Confirmed findings (1)
  • medium defer is accepted at module/program scope and rewritten into ordinary top-level statements instead of being rejected as an invalid placement. (inline)

⛔ Unresolved from previous review (3) — not approved until fixed

  • The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.
  • aot/lower/src/function.rs: return; inside an outlined try body does not propagate the enclosing-function return channel. The region scanner marks Opcode::Return0 as body_returns and the caller allocates/passes flag and value cells, but the body lowering only parks a return when matching Some(Exit::Ret(Some(reg))); Exit::Ret(None) bypasses that code and leaves the flag at its seeded false value. On the normal body-completion edge the caller therefore treats return; as a raised/catch outcome (or, for an all-return body, reaches the wrong continuation) instead of returning nil from the enclosing function. A minimal failure is try { return; } catch e { print("caught") } in a non-entry function: the VM returns immediately, while native lowering enters the catch path. This would be disproved if Return0 were converted to an Exit::Ret(Some(...)) before this match or if another lowering path sets the return flag for Ret(None), neither of which is present in the inspected code.
  • The Tier-1 hybrid bridge uses one module-global lk_hybrid_argbuf for every CallVm site, but the native runtime also supports concurrent spawned tasks. If two native threads execute a bridged call concurrently, each overwrites the shared tags/payloads before the other bridge reads them, so the VM function can receive another thread's arguments (or inconsistent tag/value pairs). The comment's single-threaded assumption is not enforced by the language/runtime.
🧹 Additional findings from this change (not shown inline) (11)
  • [high] Returns inside if let and while let bodies bypass all pending defers, causing resources to remain unreleased on those control-flow paths.
  • [high] Trait default methods containing defer retain Stmt::Defer artifacts after the front-end rewrite and are copied into impls unrevised.
  • [high] A protected-region call can marshal a Bool argument as only one byte instead of the eight-byte word required by the trampoline, so the outlined body receives garbage for Bool inputs.
  • [high] Native executable cache entries are not invalidated when the linked lkrt/runtime or toolchain changes, allowing an executable with an old ABI or runtime implementation to be reused for unchanged source.
  • [high] Tier 1 hybrid calls are not safe when native code executes concurrently: all generated CallVm sites share one writable global argument buffer, so threads can overwrite each other's tagged arguments before the bridge reads them.
  • [high] Type marks for released struct maps are never removed, so allocator address reuse can make an unrelated map appear to be the old struct type.
  • [medium] Calling set_global or get_global before registering host functions permanently prevents those registrations, despite the public contract saying registration is only required before the first eval.
  • [medium] The editor grammar does not accept several newly supported syntax forms: try is only declared as try_statement (so let x = try { ... } catch e { ... }; from examples/syntax/try_expression.lk cannot parse), and there is no defer_statement or const_statement rule even though the compiler has defer and const syntax. Tree-sitter consumers therefore produce ERROR nodes or fail highlighting/navigation for valid LK files.
  • [high] The correctness workflow and the local sanitizer target invoke a nonexistent integration-test target, examples_differential_test. cli/tests has no file or target with that name, so cargo test -p lk-cli --test examples_differential_test fails immediately with an unknown test target instead of running the advertised examples differential corpus; this makes both the scheduled sanitized job and make sanitized-differential unreproducible.
  • [medium] VS Code's newly registered lk.analyzeCurrentFile command cannot perform analysis: it executes lk-lsp --analyze &lt;relative-path&gt; and waits for a process callback, but lsp/src/main.rs unconditionally enters lk_lsp::server::run() and has no --analyze mode or positional-file handling. The command therefore starts a stdio LSP server (which may remain running until the callback is otherwise terminated) rather than emitting JSON/errors for the selected file.
  • [high] Tier-1 hybrid calls use one module-global lk_hybrid_argbuf, while the native backend now also supports spawned/concurrent execution. Two native threads entering call_vm can overwrite tags/payloads between the stores and lk_hybrid_call_*, causing the VM bridge to receive another thread's or a partially written argument list.
♻️ Previously reported (still present) (27)
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.
  • [high] Dependency form-data@4.0.5 is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.
  • [high] Dependency js-yaml@4.1.1 is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the mailto: validator scan-loop on attacker text); upgrade to at least 5.0.2.
  • [high] Dependency tmp@0.2.5 is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.
  • [medium] Dependency js-yaml@4.1.1 is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.
  • [medium] Dependency markdown-it@14.1.1 is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.
  • [medium] Dependency qs@6.15.1 is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
  • [info] Dependency memmap2@0.2.3 is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate memmap2); upgrade to at least 0.9.11.
  • [info] Dependency bare-metal@0.2.5 is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
🤖 Prompt for AI agents — all findings (39)
In core/src/stmt/defer.rs around line 104, address this finding:
`defer` is accepted at module/program scope and rewritten into ordinary top-level statements instead of being rejected as an invalid placement.

In core/src/stmt/defer.rs around line 222, address this finding:
Returns inside `if let` and `while let` bodies bypass all pending defers, causing resources to remain unreleased on those control-flow paths.

In core/src/syntax.rs around line 101, address this finding:
Trait default methods containing `defer` retain `Stmt::Defer` artifacts after the front-end rewrite and are copied into impls unrevised.

In aot/codegen/src/clif.rs around line 896, address this finding:
A protected-region call can marshal a Bool argument as only one byte instead of the eight-byte word required by the trampoline, so the outlined body receives garbage for Bool inputs.

In cli/src/native_compile.rs around line 449, address this finding:
Native executable cache entries are not invalidated when the linked lkrt/runtime or toolchain changes, allowing an executable with an old ABI or runtime implementation to be reused for unchanged source.

In aot/codegen/src/clif.rs around line 356, address this finding:
Tier 1 hybrid calls are not safe when native code executes concurrently: all generated CallVm sites share one writable global argument buffer, so threads can overwrite each other's tagged arguments before the bridge reads them.

In lkrt/src/lkdyn.rs around line 407, address this finding:
Type marks for released struct maps are never removed, so allocator address reuse can make an unrelated map appear to be the old struct type.

In api/src/lib.rs around line 153, address this finding:
Calling `set_global` or `get_global` before registering host functions permanently prevents those registrations, despite the public contract saying registration is only required before the first `eval`.

In ecosystem/tree-sitter-lk/grammar.js around line 788, address this finding:
The editor grammar does not accept several newly supported syntax forms: `try` is only declared as `try_statement` (so `let x = try { ... } catch e { ... };` from `examples/syntax/try_expression.lk` cannot parse), and there is no `defer_statement` or `const_statement` rule even though the compiler has `defer` and `const` syntax. Tree-sitter consumers therefore produce ERROR nodes or fail highlighting/navigation for valid LK files.

In .github/workflows/correctness.yml, address this finding:
The correctness workflow and the local sanitizer target invoke a nonexistent integration-test target, `examples_differential_test`. `cli/tests` has no file or target with that name, so `cargo test -p lk-cli --test examples_differential_test` fails immediately with an unknown test target instead of running the advertised examples differential corpus; this makes both the scheduled sanitized job and `make sanitized-differential` unreproducible.

In ecosystem/vsc-ext/lsp/src/extension.ts around line 272, address this finding:
VS Code's newly registered `lk.analyzeCurrentFile` command cannot perform analysis: it executes `lk-lsp --analyze <relative-path>` and waits for a process callback, but `lsp/src/main.rs` unconditionally enters `lk_lsp::server::run()` and has no `--analyze` mode or positional-file handling. The command therefore starts a stdio LSP server (which may remain running until the callback is otherwise terminated) rather than emitting JSON/errors for the selected file.

In aot/codegen/src/clif.rs, address this finding:
Tier-1 hybrid calls use one module-global `lk_hybrid_argbuf`, while the native backend now also supports spawned/concurrent execution. Two native threads entering `call_vm` can overwrite tags/payloads between the stores and `lk_hybrid_call_*`, causing the VM bridge to receive another thread's or a partially written argument list.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `form-data@4.0.5` is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the `mailto:` validator scan-loop on attacker text); upgrade to at least 5.0.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `tmp@0.2.5` is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `markdown-it@14.1.1` is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `qs@6.15.1` is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.

In Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.

In Cargo.lock, address this finding:
Dependency `memmap2@0.2.3` is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate `memmap2`); upgrade to at least 0.9.11.

In bare-metal/Cargo.lock, address this finding:
Dependency `bare-metal@0.2.5` is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.

In ecosystem/zed-ext/Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.
📜 Review details

Coverage

  • scopes: 4/7 complete

Comment thread core/src/stmt/defer.rs
const RETURN_SLOT: &str = "__lk_defer_return";

/// Rewrites a program so every `defer` runs on the way out of its function.
pub fn desugar_defers(statements: &mut Vec<Box<Stmt>>) -> Result<(), String> {

ghost Jul 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟡 Medium

defer is accepted at module/program scope and rewritten into ordinary top-level statements instead of being rejected as an invalid placement.

🧩 Analysis
  • Change relation: unknown
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In core/src/stmt/defer.rs, address this finding:
`defer` is accepted at module/program scope and rewritten into ordinary top-level statements instead of being rejected as an invalid placement.

To have the bot fix this, comment @winnowl fix.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔎 Confirmed findings (1)
  • medium Native slice views report a stale length after their source list shrinks, unlike VM slices. (inline)

⛔ Unresolved from previous review (4) — not approved until fixed

  • The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.
  • aot/lower/src/function.rs: return; inside an outlined try body does not propagate the enclosing-function return channel. The region scanner marks Opcode::Return0 as body_returns and the caller allocates/passes flag and value cells, but the body lowering only parks a return when matching Some(Exit::Ret(Some(reg))); Exit::Ret(None) bypasses that code and leaves the flag at its seeded false value. On the normal body-completion edge the caller therefore treats return; as a raised/catch outcome (or, for an all-return body, reaches the wrong continuation) instead of returning nil from the enclosing function. A minimal failure is try { return; } catch e { print("caught") } in a non-entry function: the VM returns immediately, while native lowering enters the catch path. This would be disproved if Return0 were converted to an Exit::Ret(Some(...)) before this match or if another lowering path sets the return flag for Ret(None), neither of which is present in the inspected code.
  • The Tier-1 hybrid bridge uses one module-global lk_hybrid_argbuf for every CallVm site, but the native runtime also supports concurrent spawned tasks. If two native threads execute a bridged call concurrently, each overwrites the shared tags/payloads before the other bridge reads them, so the VM function can receive another thread's arguments (or inconsistent tag/value pairs). The comment's single-threaded assumption is not enforced by the language/runtime.
  • The correctness workflow and the local sanitizer target invoke a nonexistent integration-test target, examples_differential_test. cli/tests has no file or target with that name, so cargo test -p lk-cli --test examples_differential_test fails immediately with an unknown test target instead of running the advertised examples differential corpus; this makes both the scheduled sanitized job and make sanitized-differential unreproducible.
🧹 Additional findings from this change (not shown inline) (12)
  • [high] defer is accepted and rewritten at program scope even though the documented placement is only a function body.
  • [low] Macro rules whose first token is the intended internal marker @ remain directly invocable by callers.
  • [medium] Rewriting a statement-level try expression drops its original statement span.
  • [high] Structural equality does not enforce the advertised MAX_VALUE_DEPTH bound for nested struct fields, so comparing sufficiently deep or cyclic object graphs can recurse until the Rust stack overflows instead of returning a catchable comparison error.
  • [high] A typed map boxed into Dyn cannot be indexed or accessed by a dynamic key, even though the lowering explicitly supports boxing all five typed-map carriers. lkrt_dyn_get routes a string key to lkrt_dyn_field, and lkrt_dyn_field rejects every tag other than DYN_MAP; typed maps use DYN_TMAP_BASE..DYN_TMAP_END. Thus a program such as let m: Map&lt;str, Int&gt; = ...; let d: Dyn = m; println(d["k"]) (or a typed map placed in a mixed list and then indexed) raises natively while the VM returns the value. Integer-keyed typed maps are likewise rejected by lkrt_dyn_index, which only accepts DYN_LIST. This violates the newly added typed-map Dyn representation contract and dynamic boundary semantics.
  • [medium] The exported f64 list out-parameter bridge dereferences null output pointers instead of rejecting them, so malformed C callers can crash the process.
  • [medium] Coverage does not use the file's parse options or macro/import expansion context, so lk coverage can reject or analyze valid programs differently from lk check/lk compile. run_coverage_report reads the file and calls parse_program_source(source, ParseOptions::default()), whereas the other CLI paths use parse_options_for_file(path) and expand_program_source; a file using a configured procedural macro (or syntax requiring the file's package/base-dir context) therefore has no provider/base directory during coverage and can fail before the coverage report or omit the expanded code. This is introduced by the new coverage path; it would be disproven if parse_program_source itself were shown to perform the same file-aware expansion/provider resolution, or if coverage were intentionally documented as source-only and all such files were rejected by the contract.
  • [high] Workspace preload follows directory symlinks without cycle protection, allowing a symlink inside the workspace to recurse indefinitely and crash or hang the LSP before the file-count cap is reached. collect_lk_files_inner tests path.is_dir() and recursively visits it, but never uses symlink_metadata, tracks visited directory identities, or rejects symlink directories; a workspace containing loop -&gt; . (or a longer symlink cycle) repeatedly enters the same directories because out.len() only limits discovered .lk files. This violates the workspace file-handling/performance safety requirement; it would be false only if the supported filesystem/API guaranteed Path::is_dir does not follow symlinks or the workspace cache were proven never to preload user-controlled symlinked workspaces.
  • [high] The native execution cache key omits bundled file-import contents even though this change now lowers/bundles file imports. cached_native_executable_path hashes only the entry path, entry source bytes, package version, selected environment, and compiler executable metadata, and explicitly documents that imported-module content is not included; meanwhile compile_native_executable_from_artifact calls bundle_file_imports and embeds imported functions/constants. With LK_NATIVE_RUN=1, changing use "helper.lk"'s implementation leaves the entry source hash unchanged, so try_execute_cached_native executes the old cached binary and returns stale behavior until some unrelated cache key changes. This would be disproven only if native caching were disabled whenever file imports exist or the cache directory were independently invalidated on imported-file changes.
  • [medium] The benchmark README still documents the retired LLVM/native-cache model and contradicts the current benchmark script/backend. It tells users that direct execution can use a cached native fast path when “LLVM lowering” succeeds and says the full-suite AOT smoke skips on loop-after-dynamic-map GetIndex, while the changed runner explicitly uses Cranelift, invokes lk compile with LK_AOT_NO_FALLBACK=1, and reports a failed native compile rather than silently treating it as AOT. A maintainer following the README can therefore interpret VM/native measurements and AOT skips incorrectly (and the README's historical numbers are presented alongside the current instructions as if they describe this implementation). This is a documentation compatibility failure introduced/exposed by the backend migration; it would be false only if the current CLI still selected an LLVM backend or the runner still allowed the documented fallback behavior.
  • [medium] docs/aot/aot-gaps-and-lkrt.md is labeled as a current lowering/ABI plan and contains implementation guidance that names deleted or no-longer-current components (llvm/src/llvm/..., llvm/src/llvm/dynamic_containers, lkrt/src/containers.rs, native_dynamic_*_helpers, and @lkrt_list_*), while the repository's current AOT implementation is the aot/* Cranelift/MIR pipeline and the referenced legacy files are absent. A maintainer using this document to diagnose coverage or implement the stated next steps will search the wrong paths and may reintroduce the retired ABI, violating the obligation that public AOT/package guidance describe the actual backend and limitations. This would be false only if those legacy LLVM/container modules still existed and were the supported implementation, rather than the documented historical context being stale.
  • [high] Tier-1 hybrid calls are not thread-safe even though the language/runtime exposes concurrency: every generated native function writes its tagged arguments into the single module-global lk_hybrid_argbuf before entering the API bridge's mutex. Two concurrent spawn/thread executions can overwrite slots between the first write and lk_hybrid_call_*, causing the VM callee to receive another call's arguments (and possibly the wrong argc contents). The mutex inside api cannot protect this because the buffer is populated before the bridge acquires it; the design needs a per-call buffer or a lock covering marshaling plus the bridge call.
♻️ Previously reported (still present) (27)
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.
  • [high] Dependency form-data@4.0.5 is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.
  • [high] Dependency js-yaml@4.1.1 is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the mailto: validator scan-loop on attacker text); upgrade to at least 5.0.2.
  • [high] Dependency tmp@0.2.5 is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.
  • [medium] Dependency js-yaml@4.1.1 is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.
  • [medium] Dependency markdown-it@14.1.1 is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.
  • [medium] Dependency qs@6.15.1 is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
  • [info] Dependency memmap2@0.2.3 is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate memmap2); upgrade to at least 0.9.11.
  • [info] Dependency bare-metal@0.2.5 is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
🤖 Prompt for AI agents — all findings (40)
In core/src/syntax.rs, address this finding:
`defer` is accepted and rewritten at program scope even though the documented placement is only a function body.

In core/src/macro_system.rs around line 624, address this finding:
Macro rules whose first token is the intended internal marker `@` remain directly invocable by callers.

In core/src/stmt/defer.rs around line 279, address this finding:
Rewriting a statement-level try expression drops its original statement span.

In core/src/val/runtime_model/equality.rs around line 163, address this finding:
Structural equality does not enforce the advertised MAX_VALUE_DEPTH bound for nested struct fields, so comparing sufficiently deep or cyclic object graphs can recurse until the Rust stack overflows instead of returning a catchable comparison error.

In lkrt/src/lkdyn.rs around line 974, address this finding:
A typed map boxed into `Dyn` cannot be indexed or accessed by a dynamic key, even though the lowering explicitly supports boxing all five typed-map carriers. `lkrt_dyn_get` routes a string key to `lkrt_dyn_field`, and `lkrt_dyn_field` rejects every tag other than `DYN_MAP`; typed maps use `DYN_TMAP_BASE..DYN_TMAP_END`. Thus a program such as `let m: Map<str, Int> = ...; let d: Dyn = m; println(d["k"])` (or a typed map placed in a mixed list and then indexed) raises natively while the VM returns the value. Integer-keyed typed maps are likewise rejected by `lkrt_dyn_index`, which only accepts `DYN_LIST`. This violates the newly added typed-map Dyn representation contract and dynamic boundary semantics.

In lkrt/src/lkslice.rs around line 101, address this finding:
Native slice views report a stale length after their source list shrinks, unlike VM slices.

In lkrt/src/lklist.rs around line 1073, address this finding:
The exported f64 list out-parameter bridge dereferences null output pointers instead of rejecting them, so malformed C callers can crash the process.

In cli/src/coverage.rs around line 51, address this finding:
Coverage does not use the file's parse options or macro/import expansion context, so `lk coverage` can reject or analyze valid programs differently from `lk check`/`lk compile`. `run_coverage_report` reads the file and calls `parse_program_source(source, ParseOptions::default())`, whereas the other CLI paths use `parse_options_for_file(path)` and `expand_program_source`; a file using a configured procedural macro (or syntax requiring the file's package/base-dir context) therefore has no provider/base directory during coverage and can fail before the coverage report or omit the expanded code. This is introduced by the new coverage path; it would be disproven if `parse_program_source` itself were shown to perform the same file-aware expansion/provider resolution, or if coverage were intentionally documented as source-only and all such files were rejected by the contract.

In lsp/src/server/workspace_cache.rs around line 398, address this finding:
Workspace preload follows directory symlinks without cycle protection, allowing a symlink inside the workspace to recurse indefinitely and crash or hang the LSP before the file-count cap is reached. `collect_lk_files_inner` tests `path.is_dir()` and recursively visits it, but never uses `symlink_metadata`, tracks visited directory identities, or rejects symlink directories; a workspace containing `loop -> .` (or a longer symlink cycle) repeatedly enters the same directories because `out.len()` only limits discovered `.lk` files. This violates the workspace file-handling/performance safety requirement; it would be false only if the supported filesystem/API guaranteed `Path::is_dir` does not follow symlinks or the workspace cache were proven never to preload user-controlled symlinked workspaces.

In cli/src/native_compile.rs, address this finding:
The native execution cache key omits bundled file-import contents even though this change now lowers/bundles file imports. `cached_native_executable_path` hashes only the entry path, entry source bytes, package version, selected environment, and compiler executable metadata, and explicitly documents that imported-module content is not included; meanwhile `compile_native_executable_from_artifact` calls `bundle_file_imports` and embeds imported functions/constants. With `LK_NATIVE_RUN=1`, changing `use "helper.lk"`'s implementation leaves the entry source hash unchanged, so `try_execute_cached_native` executes the old cached binary and returns stale behavior until some unrelated cache key changes. This would be disproven only if native caching were disabled whenever file imports exist or the cache directory were independently invalidated on imported-file changes.

In bench/README.md, address this finding:
The benchmark README still documents the retired LLVM/native-cache model and contradicts the current benchmark script/backend. It tells users that direct execution can use a cached native fast path when “LLVM lowering” succeeds and says the full-suite AOT smoke skips on loop-after-dynamic-map `GetIndex`, while the changed runner explicitly uses Cranelift, invokes `lk compile` with `LK_AOT_NO_FALLBACK=1`, and reports a failed native compile rather than silently treating it as AOT. A maintainer following the README can therefore interpret VM/native measurements and AOT skips incorrectly (and the README's historical numbers are presented alongside the current instructions as if they describe this implementation). This is a documentation compatibility failure introduced/exposed by the backend migration; it would be false only if the current CLI still selected an LLVM backend or the runner still allowed the documented fallback behavior.

In docs/aot/aot-gaps-and-lkrt.md, address this finding:
`docs/aot/aot-gaps-and-lkrt.md` is labeled as a current lowering/ABI plan and contains implementation guidance that names deleted or no-longer-current components (`llvm/src/llvm/...`, `llvm/src/llvm/dynamic_containers`, `lkrt/src/containers.rs`, `native_dynamic_*_helpers`, and `@lkrt_list_*`), while the repository's current AOT implementation is the `aot/*` Cranelift/MIR pipeline and the referenced legacy files are absent. A maintainer using this document to diagnose coverage or implement the stated next steps will search the wrong paths and may reintroduce the retired ABI, violating the obligation that public AOT/package guidance describe the actual backend and limitations. This would be false only if those legacy LLVM/container modules still existed and were the supported implementation, rather than the documented historical context being stale.

In aot/codegen/src/clif.rs, address this finding:
Tier-1 hybrid calls are not thread-safe even though the language/runtime exposes concurrency: every generated native function writes its tagged arguments into the single module-global `lk_hybrid_argbuf` before entering the API bridge's mutex. Two concurrent `spawn`/thread executions can overwrite slots between the first write and `lk_hybrid_call_*`, causing the VM callee to receive another call's arguments (and possibly the wrong argc contents). The mutex inside `api` cannot protect this because the buffer is populated before the bridge acquires it; the design needs a per-call buffer or a lock covering marshaling plus the bridge call.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `form-data@4.0.5` is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the `mailto:` validator scan-loop on attacker text); upgrade to at least 5.0.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `tmp@0.2.5` is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `markdown-it@14.1.1` is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `qs@6.15.1` is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.

In Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.

In Cargo.lock, address this finding:
Dependency `memmap2@0.2.3` is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate `memmap2`); upgrade to at least 0.9.11.

In bare-metal/Cargo.lock, address this finding:
Dependency `bare-metal@0.2.5` is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.

In ecosystem/zed-ext/Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.
📜 Review details

Coverage

  • scopes: 5/7 complete

Comment thread lkrt/src/lkslice.rs
/// # Safety
/// `handle` must be a live window handle, or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lkrt_lkslice_i64_len(handle: *mut c_void) -> i64 {

ghost Jul 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟡 Medium

Native slice views report a stale length after their source list shrinks, unlike VM slices.

🧩 Analysis
  • Change relation: unknown
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In lkrt/src/lkslice.rs, address this finding:
Native slice views report a stale length after their source list shrinks, unlike VM slices.

To have the bot fix this, comment @winnowl fix.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🔎 Confirmed findings (3)
  • high Structural equality of nested objects bypasses the depth guard and can recurse until Rust stack overflow. (inline)
  • medium User mappings do not enforce execute/write separation: every user leaf, including the user task stack and the one-shot user stack, is installed with PRESENT|WRITE|USER, and the boot path never enables EFER.NXE or sets the NX bit. Consequently ring 3 can execute arbitrary bytes it writes to its writable stack (and modify code pages), so a stack/code memory-corruption bug is not contained by page execute permissions. (inline)
  • medium Failed user-address-space construction leaks already allocated page-table pages. build_user_space allocates pml4, pdpt, directory, and table independently, but if any allocation is zero it immediately returns 0 without releasing the nonzero pages allocated earlier; later spawn_user_task then reports failure while those pages remain counted as allocated. Repeated creation attempts under memory pressure therefore permanently consume page allocator resources and can prevent otherwise valid tasks or driver buffers from being created. (inline)

⛔ Unresolved from previous review (6) — not approved until fixed

  • The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.
  • aot/lower/src/function.rs: return; inside an outlined try body does not propagate the enclosing-function return channel. The region scanner marks Opcode::Return0 as body_returns and the caller allocates/passes flag and value cells, but the body lowering only parks a return when matching Some(Exit::Ret(Some(reg))); Exit::Ret(None) bypasses that code and leaves the flag at its seeded false value. On the normal body-completion edge the caller therefore treats return; as a raised/catch outcome (or, for an all-return body, reaches the wrong continuation) instead of returning nil from the enclosing function. A minimal failure is try { return; } catch e { print("caught") } in a non-entry function: the VM returns immediately, while native lowering enters the catch path. This would be disproved if Return0 were converted to an Exit::Ret(Some(...)) before this match or if another lowering path sets the return flag for Ret(None), neither of which is present in the inspected code.
  • The Tier-1 hybrid bridge uses one module-global lk_hybrid_argbuf for every CallVm site, but the native runtime also supports concurrent spawned tasks. If two native threads execute a bridged call concurrently, each overwrites the shared tags/payloads before the other bridge reads them, so the VM function can receive another thread's arguments (or inconsistent tag/value pairs). The comment's single-threaded assumption is not enforced by the language/runtime.
  • cli/src/native_compile.rs: Native executable cache entries are not invalidated when the linked lkrt/runtime or toolchain changes, allowing an executable with an old ABI or runtime implementation to be reused for unchanged source.
  • aot/codegen/src/clif.rs: Tier 1 hybrid calls are not safe when native code executes concurrently: all generated CallVm sites share one writable global argument buffer, so threads can overwrite each other's tagged arguments before the bridge reads them.
  • The correctness workflow and the local sanitizer target invoke a nonexistent integration-test target, examples_differential_test. cli/tests has no file or target with that name, so cargo test -p lk-cli --test examples_differential_test fails immediately with an unknown test target instead of running the advertised examples differential corpus; this makes both the scheduled sanitized job and make sanitized-differential unreproducible.
🧹 Additional findings from this change (not shown inline) (9)
  • [medium] A defer at program scope is silently accepted and rewritten instead of being rejected as invalid placement. desugar_defers calls rewrite_sequence(&amp;mut program.statements) before it ever validates that the sequence belongs to a function, so input such as defer cleanup(); becomes a top-level cleanup() statement (and a top-level defer before a function can inject cleanup at program EOF). This violates the documented function-body-only grammar boundary and changes program behavior rather than producing the required diagnostic. This is introduced by the new desugaring entry point; it would be disproved if the parser or desugaring path rejected a root-level Stmt::Defer before rewriting.
  • [high] Trait default methods containing defer bypass the defer rewrite and are copied into impls with Stmt::Defer still present. desugar_defers only descends through Stmt::Impl methods and has no Stmt::Trait arm, while trait method bodies are stored separately in default_methods; apply_trait_defaults then clones those untouched bodies into impl.methods. For example, a default method with defer release(); return ... reaches type checking/compiler as a defer node, contrary to the invariant that no downstream phase sees defer and contrary to the method's release semantics. This is introduced by the ordering of the new passes; it would be false if trait default bodies were independently rewritten (or otherwise proven to contain no defer) before being copied.
  • [low] The template interpolation scanner treats braces inside string literals (and similarly other lexical subregions) as structural interpolation braces. split_template_string increments/decrements depth on every {/} while inside ${...} without recognizing quoted strings, so a valid hole such as ${show("}")} is closed at the } inside the string and the remaining brace is treated as literal text. Macro expansion and the parser share this scanner, therefore such templates are mis-split and can produce a malformed-expression diagnostic or alter the literal output instead of preserving the hole. This is introduced by centralizing the scanner; it would be disproved if the language explicitly forbids braces in quoted expressions, or if the tokenizer/parser guarantees those braces cannot occur in template-hole text.
  • [medium] The trait-default rewrite does not erase the default-only representation after copying methods into impls. apply_trait_defaults collects Stmt::Trait.default_methods, appends clones to matching impls, but never clears default_methods on the trait. Thus downstream AST consumers still receive a trait carrying bodies in the special default-method field, violating the stated rewrite invariant that no default-only representation remains; any consumer that walks or serializes trait defaults can observe/process the body a second time. This is introduced by the new rewrite; it would be disproved if all downstream consumers intentionally ignore default_methods and the invariant were narrowed to impl dispatch only.
  • [high] Native slices do not implement the VM's live-source bounds semantics after the source shrinks.
  • [high] Boolean values crossing an outlined try statement are marshalled as one-byte stores but the C trampoline always reads an 8-byte long long, so the body can receive garbage bits instead of the original Bool.
  • [high] The Tier-1 hybrid argument buffer is a single mutable module-global, but native spawn/go execute compiled callbacks on OS threads. Two concurrent hybrid calls (or a callback racing the parent) overwrite tags/values between marshaling and lk_hybrid_call_*, causing the VM to receive another call's arguments.
  • [medium] The bare-metal footprint documentation still advertises a removed slice stdlib module as a selectable feature.
  • [high] The native dynamic-value work adds Set and Bytes carrier tags (and documents them as values that can cross a bridged return), but the hybrid VM bridge still only marshals Nil/Bool/Int/Float/Str/List/Map: any VM function returning a Set or Bytes reaches the Some(other) =&gt; hybrid_die(...) arm and terminates instead of returning the new LkDyn. The public header likewise advertises tags only through MAP, so a Tier-1 hybrid caller cannot consume these values despite the runtime/ABI supporting them.
♻️ Previously reported (still present) (27)
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.
  • [high] Dependency form-data@4.0.5 is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.
  • [high] Dependency js-yaml@4.1.1 is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the mailto: validator scan-loop on attacker text); upgrade to at least 5.0.2.
  • [high] Dependency tmp@0.2.5 is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.
  • [medium] Dependency js-yaml@4.1.1 is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.
  • [medium] Dependency markdown-it@14.1.1 is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.
  • [medium] Dependency qs@6.15.1 is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
  • [info] Dependency memmap2@0.2.3 is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate memmap2); upgrade to at least 0.9.11.
  • [info] Dependency bare-metal@0.2.5 is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
🤖 Prompt for AI agents — all findings (39)
In core/src/stmt/defer.rs, address this finding:
A `defer` at program scope is silently accepted and rewritten instead of being rejected as invalid placement. `desugar_defers` calls `rewrite_sequence(&mut program.statements)` before it ever validates that the sequence belongs to a function, so input such as `defer cleanup();` becomes a top-level `cleanup()` statement (and a top-level `defer` before a function can inject cleanup at program EOF). This violates the documented function-body-only grammar boundary and changes program behavior rather than producing the required diagnostic. This is introduced by the new desugaring entry point; it would be disproved if the parser or desugaring path rejected a root-level `Stmt::Defer` before rewriting.

In core/src/stmt/defer.rs around line 144, address this finding:
Trait default methods containing `defer` bypass the defer rewrite and are copied into impls with `Stmt::Defer` still present. `desugar_defers` only descends through `Stmt::Impl` methods and has no `Stmt::Trait` arm, while trait method bodies are stored separately in `default_methods`; `apply_trait_defaults` then clones those untouched bodies into `impl.methods`. For example, a default method with `defer release(); return ...` reaches type checking/compiler as a defer node, contrary to the invariant that no downstream phase sees defer and contrary to the method's release semantics. This is introduced by the ordering of the new passes; it would be false if trait default bodies were independently rewritten (or otherwise proven to contain no defer) before being copied.

In core/src/token.rs around line 51, address this finding:
The template interpolation scanner treats braces inside string literals (and similarly other lexical subregions) as structural interpolation braces. `split_template_string` increments/decrements `depth` on every `{`/`}` while inside `${...}` without recognizing quoted strings, so a valid hole such as `${show("}")}` is closed at the `}` inside the string and the remaining brace is treated as literal text. Macro expansion and the parser share this scanner, therefore such templates are mis-split and can produce a malformed-expression diagnostic or alter the literal output instead of preserving the hole. This is introduced by centralizing the scanner; it would be disproved if the language explicitly forbids braces in quoted expressions, or if the tokenizer/parser guarantees those braces cannot occur in template-hole text.

In core/src/stmt/trait_defaults.rs around line 83, address this finding:
The trait-default rewrite does not erase the default-only representation after copying methods into impls. `apply_trait_defaults` collects `Stmt::Trait.default_methods`, appends clones to matching impls, but never clears `default_methods` on the trait. Thus downstream AST consumers still receive a trait carrying bodies in the special default-method field, violating the stated rewrite invariant that no default-only representation remains; any consumer that walks or serializes trait defaults can observe/process the body a second time. This is introduced by the new rewrite; it would be disproved if all downstream consumers intentionally ignore `default_methods` and the invariant were narrowed to impl dispatch only.

In lkrt/src/lkslice.rs around line 101, address this finding:
Native slices do not implement the VM's live-source bounds semantics after the source shrinks.

In core/src/val/runtime_model/equality.rs around line 163, address this finding:
Structural equality of nested objects bypasses the depth guard and can recurse until Rust stack overflow.

In aot/codegen/src/clif.rs around line 896, address this finding:
Boolean values crossing an outlined `try` statement are marshalled as one-byte stores but the C trampoline always reads an 8-byte `long long`, so the body can receive garbage bits instead of the original Bool.

In aot/codegen/src/clif.rs around line 356, address this finding:
The Tier-1 hybrid argument buffer is a single mutable module-global, but native `spawn`/`go` execute compiled callbacks on OS threads. Two concurrent hybrid calls (or a callback racing the parent) overwrite tags/values between marshaling and `lk_hybrid_call_*`, causing the VM to receive another call's arguments.

In bare-metal-x86/drivers/paging.lk around line 26, address this finding:
User mappings do not enforce execute/write separation: every user leaf, including the user task stack and the one-shot user stack, is installed with PRESENT|WRITE|USER, and the boot path never enables EFER.NXE or sets the NX bit. Consequently ring 3 can execute arbitrary bytes it writes to its writable stack (and modify code pages), so a stack/code memory-corruption bug is not contained by page execute permissions.

In bare-metal-x86/program.lk around line 2811, address this finding:
Failed user-address-space construction leaks already allocated page-table pages. `build_user_space` allocates pml4, pdpt, directory, and table independently, but if any allocation is zero it immediately returns 0 without releasing the nonzero pages allocated earlier; later `spawn_user_task` then reports failure while those pages remain counted as allocated. Repeated creation attempts under memory pressure therefore permanently consume page allocator resources and can prevent otherwise valid tasks or driver buffers from being created.

In bare-metal/README.md around line 120, address this finding:
The bare-metal footprint documentation still advertises a removed `slice` stdlib module as a selectable feature.

In api/src/lib.rs, address this finding:
The native dynamic-value work adds Set and Bytes carrier tags (and documents them as values that can cross a bridged return), but the hybrid VM bridge still only marshals Nil/Bool/Int/Float/Str/List/Map: any VM function returning a Set or Bytes reaches the `Some(other) => hybrid_die(...)` arm and terminates instead of returning the new `LkDyn`. The public header likewise advertises tags only through MAP, so a Tier-1 hybrid caller cannot consume these values despite the runtime/ABI supporting them.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `form-data@4.0.5` is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the `mailto:` validator scan-loop on attacker text); upgrade to at least 5.0.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `tmp@0.2.5` is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `markdown-it@14.1.1` is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `qs@6.15.1` is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.

In Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.

In Cargo.lock, address this finding:
Dependency `memmap2@0.2.3` is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate `memmap2`); upgrade to at least 0.9.11.

In bare-metal/Cargo.lock, address this finding:
Dependency `bare-metal@0.2.5` is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.

In ecosystem/zed-ext/Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.
📜 Review details

Coverage

  • scopes: 5/5 complete

let Some(right_value) = right.fields.get(name) else {
return Ok(false);
};
if !self.values(left_value, right_value, depth + 1)? {

ghost Jul 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Error Handling | 🟠 High

Structural equality of nested objects bypasses the depth guard and can recurse until Rust stack overflow.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ I did not execute a reproducer, so the exact Rust stack-overflow threshold depends on build/runtime stack size; the unbounded recursive path is established from the code.
🤖 Prompt for AI agents
In core/src/val/runtime_model/equality.rs, address this finding:
Structural equality of nested objects bypasses the depth guard and can recurse until Rust stack overflow.

To have the bot fix this, comment @winnowl fix.

// how the first ring-3 program here failed, on its own first instruction.
const PAGE_PRESENT = 0x1;
const PAGE_WRITE = 0x2;
const PAGE_USER = 0x4;

ghost Jul 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security | 🟡 Medium

User mappings do not enforce execute/write separation: every user leaf, including the user task stack and the one-shot user stack, is installed with PRESENT|WRITE|USER, and the boot path never enables EFER.NXE or sets the NX bit. Consequently ring 3 can execute arbitrary bytes it writes to its writable stack (and modify code pages), so a stack/code memory-corruption bug is not contained by page execute permissions.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ The exact severity depends on the intended threat model and whether the platform guarantees CPUID.NX, but on the x86-64/QEMU target the missing NXE setup is sufficient to make NX bits ineffective.
🤖 Prompt for AI agents
In bare-metal-x86/drivers/paging.lk, address this finding:
User mappings do not enforce execute/write separation: every user leaf, including the user task stack and the one-shot user stack, is installed with PRESENT|WRITE|USER, and the boot path never enables EFER.NXE or sets the NX bit. Consequently ring 3 can execute arbitrary bytes it writes to its writable stack (and modify code pages), so a stack/code memory-corruption bug is not contained by page execute permissions.

To have the bot fix this, comment @winnowl fix.

Comment thread bare-metal-x86/program.lk
let pdpt = page_alloc(SHARED_PAGES);
let directory = page_alloc(SHARED_PAGES);
let table = page_alloc(SHARED_PAGES);
if (pml4 == 0 || pdpt == 0 || directory == 0 || table == 0) {

ghost Jul 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Resource Lifetime | 🟡 Medium

Failed user-address-space construction leaks already allocated page-table pages. build_user_space allocates pml4, pdpt, directory, and table independently, but if any allocation is zero it immediately returns 0 without releasing the nonzero pages allocated earlier; later spawn_user_task then reports failure while those pages remain counted as allocated. Repeated creation attempts under memory pressure therefore permanently consume page allocator resources and can prevent otherwise valid tasks or driver buffers from being created.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ The current image invokes build_user_space only twice during startup, so the practical leak is bounded by those attempts unless future code or a caller adds retries; that does not invalidate the allocation failure bug.
🤖 Prompt for AI agents
In bare-metal-x86/program.lk, address this finding:
Failed user-address-space construction leaks already allocated page-table pages. `build_user_space` allocates pml4, pdpt, directory, and table independently, but if any allocation is zero it immediately returns 0 without releasing the nonzero pages allocated earlier; later `spawn_user_task` then reports failure while those pages remain counted as allocated. Repeated creation attempts under memory pressure therefore permanently consume page allocator resources and can prevent otherwise valid tasks or driver buffers from being created.

To have the bot fix this, comment @winnowl fix.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔎 Confirmed findings (2)
  • medium The native runtime exposes privileged system-control ABI functions in the hosted std profile, so a program that calls cpu_load_idt/the other system helpers on a normal host executes lidt/lgdt/mov cr* instead of receiving the VM's documented bare-metal refusal. For example, lkrt_cpu_load_idt has an x86 implementation with no feature = "std" guard, and lkrt/src/lib.rs re-exports it unconditionally; on hosted x86_64 this can fault or terminate the process rather than produce a catchable refusal. The VM counterpart is compiled only under not(feature = "std") and otherwise returns cpu_load_idt requires bare-metal execution on x86-64. This capability exposure was introduced by registering/exporting the new system ABI family; it would be disproven if hosted AOT lowering could prove these symbols unreachable or if the std implementation itself rejected rather than executing the privileged instruction. (inline)
  • high The ring-3 syscall trampoline enters the compiled dispatcher with a misaligned stack: after the CPU's ring transition, the seven pushes leave RSP 16-byte aligned, but sub rsp, 264 changes it to RSP%16 == 8 immediately before call lk_syscall_dispatch (the ABI requires the caller's RSP to be 0 mod 16 before a call). A dispatcher or callee that emits an aligned SSE stack spill can fault or misbehave on every syscall, so the user boundary is not ABI-correct; reserving 256 bytes (or otherwise explicitly aligning) would preserve the required phase. This is introduced by the new syscall trampoline; it would be disproved only if the actual native LK calling convention explicitly requires the opposite stack phase and all possible callees avoid alignment-sensitive spills. (inline)

⛔ Unresolved from previous review (9) — not approved until fixed

  • The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.
  • aot/lower/src/function.rs: return; inside an outlined try body does not propagate the enclosing-function return channel. The region scanner marks Opcode::Return0 as body_returns and the caller allocates/passes flag and value cells, but the body lowering only parks a return when matching Some(Exit::Ret(Some(reg))); Exit::Ret(None) bypasses that code and leaves the flag at its seeded false value. On the normal body-completion edge the caller therefore treats return; as a raised/catch outcome (or, for an all-return body, reaches the wrong continuation) instead of returning nil from the enclosing function. A minimal failure is try { return; } catch e { print("caught") } in a non-entry function: the VM returns immediately, while native lowering enters the catch path. This would be disproved if Return0 were converted to an Exit::Ret(Some(...)) before this match or if another lowering path sets the return flag for Ret(None), neither of which is present in the inspected code.
  • The Tier-1 hybrid bridge uses one module-global lk_hybrid_argbuf for every CallVm site, but the native runtime also supports concurrent spawned tasks. If two native threads execute a bridged call concurrently, each overwrites the shared tags/payloads before the other bridge reads them, so the VM function can receive another thread's arguments (or inconsistent tag/value pairs). The comment's single-threaded assumption is not enforced by the language/runtime.
  • cli/src/native_compile.rs: Native executable cache entries are not invalidated when the linked lkrt/runtime or toolchain changes, allowing an executable with an old ABI or runtime implementation to be reused for unchanged source.
  • aot/codegen/src/clif.rs: Tier 1 hybrid calls are not safe when native code executes concurrently: all generated CallVm sites share one writable global argument buffer, so threads can overwrite each other's tagged arguments before the bridge reads them.
  • The correctness workflow and the local sanitizer target invoke a nonexistent integration-test target, examples_differential_test. cli/tests has no file or target with that name, so cargo test -p lk-cli --test examples_differential_test fails immediately with an unknown test target instead of running the advertised examples differential corpus; this makes both the scheduled sanitized job and make sanitized-differential unreproducible.
  • core/src/val/runtime_model/equality.rs: Structural equality of nested objects bypasses the depth guard and can recurse until Rust stack overflow.
  • aot/codegen/src/clif.rs: The Tier-1 hybrid argument buffer is a single mutable module-global, but native spawn/go execute compiled callbacks on OS threads. Two concurrent hybrid calls (or a callback racing the parent) overwrite tags/values between marshaling and lk_hybrid_call_*, causing the VM to receive another call's arguments.
  • The native dynamic-value work adds Set and Bytes carrier tags (and documents them as values that can cross a bridged return), but the hybrid VM bridge still only marshals Nil/Bool/Int/Float/Str/List/Map: any VM function returning a Set or Bytes reaches the Some(other) => hybrid_die(...) arm and terminates instead of returning the new LkDyn. The public header likewise advertises tags only through MAP, so a Tier-1 hybrid caller cannot consume these values despite the runtime/ABI supporting them.
🧹 Additional findings from this change (not shown inline) (15)
  • [medium] A top-level defer is silently accepted and rewritten instead of being rejected as an unsupported placement.
  • [high] Generated stdlib calls reject valid calls that omit parameters having defaults because required_params counts every non-optional parameter as required, ignoring has_default.
  • [high] Stdlib signature checking skips type validation for supplied optional parameters, allowing arguments of incompatible types through the checker.
  • [high] Deep or cyclic object values can still recurse without the new depth bound during equality.
  • [high] Statement-form try regions can pass a Bool input with an invalid machine-word representation. TryRegionCall stores each argument directly into an 8-byte slot, so an I8 Bool writes only one byte while lkrt_rt_try_region reads a long long; the remaining bytes are uninitialized stack data. A function such as fn f(b) { try { if b { ... } } catch e { ... } } can therefore observe a nondeterministic/non-0-or-1 value (or take the wrong branch), despite Bool being explicitly admitted as a crossing word. This is introduced by the new Bool try-boundary support; it would be disproved only if Cranelift's stack_store widened I8 to an initialized 8-byte slot, which its typed store does not do.
  • [high] The try-region arity check omits the two return-channel cells for a body containing an enclosing-function return. function.rs accepts try_body_params + try_body_cells &lt;= 8, then unconditionally appends two more cells for the return flag/value. A region with seven crossing input/cell words and a return consequently emits a 9-argument body call, while lkrt_rt_try_region only has cases 0..8 and traps in its default (and the native lowering does not reject it). Thus a supported VM program can compile to a native binary that traps rather than taking its return path. This is introduced by the new return-channel outlining; it would be false only if those two channel cells were not actually appended or the trampoline supported more than eight arguments.
  • [high] Hybrid bridge calls use one module-global argument buffer for all native call sites. lk_hybrid_argbuf is a single shared writable data object, and call_vm writes tags/payloads into it before invoking the VM bridge; there is no lock, per-call stack buffer, or thread-local storage. If two native threads concurrently execute hybrid calls (for example, native concurrency/spawn code reaches VM-executed functions), one call can overwrite another's arguments between its writes and lk_hybrid_call_*, causing the VM function to receive mixed arguments. The claim would be disproved only if hybrid execution were provably single-threaded for the lifetime of every generated module; the surrounding native pipeline exposes concurrent execution, so the code's explicit single-threaded assumption is not a safe invariant.
  • [medium] lkrt_str_repeat silently returns an empty string for negative counts, while the VM/core string method rejects them with string.repeat() count must be non-negative, got {n}. A native program such as "x".repeat(-1) therefore succeeds with "" (or a module call lowered to this symbol), whereas the VM raises a catchable error; this also bypasses the stdlib's documented count validation. The changed native helper explicitly implements if n &lt;= 0 { return empty }, so this is a concrete host/native parity and invalid-input failure, not merely missing validation. It would be disproven if all native call sites statically exclude negative values or if the language contract intentionally changed negative repeat to empty in the same revision.
  • [medium] build_user_space leaks already allocated page-table pages when any later allocation fails: it performs four page_alloc calls and immediately returns 0 if any is zero, without releasing the nonzero pages. Under page pressure (or after an allocator failure), repeated user-task/address-space creation permanently consumes up to three pages per failed attempt, and the caller does not roll those pages back either. This violates the resource-lifetime obligation on failure paths and can turn a recoverable temporary exhaustion into permanent inability to create tasks; the claim would be false only if page_alloc were guaranteed all-or-nothing/reserving four pages (it is not) or another caller reliably reclaimed the partial allocations.
  • [high] Dynamic interrupt installation is not race-safe across the IDT gate and runtime handler table. install_device_handler writes a present gate first, then writes lkrt_isr_handlers[vector]; with interrupts already enabled at this point, an interrupt for a vector whose line is currently unmasked can arrive in that interval. The generic ISR sees the still-zero handler, skips the call and returns with iretq without the device driver's EOI, leaving the PIC line in service (and potentially losing/stalling that device permanently). The driver comments rely on unmasking last, but the helper itself does not mask/disable interrupts, and it is also reused for vectors that may already be installed. This would be disproved only if every caller could prove the line is masked and cannot have a pending delivery for the entire gate/table update, an invariant the exported helper does not enforce.
  • [high] The Tier 0 bundle path cannot execute a bundled source file that contains a relative file import.
  • [high] File bundling drops imported struct declarations (and trait declarations) from the merged artifact, so imported constructors/type identity are not preserved for native lowering.
  • [medium] The committed Tree-sitter grammar does not recognize the newly documented defer statement. defer is a lexer keyword and is used throughout examples/syntax/defer.lk, but _statement has no defer rule and there is no defer rule anywhere in grammar.js; the generated grammar/parser artifacts consequently cannot produce a defer node and will recover with an error around each defer. This makes Tree-sitter parsing and editor grammar behavior disagree with the compiler for a documented construct. This is introduced by the scope's expanded-language grammar update; it would be disproved only if the generated parser accepted defer ...; as a named non-error construct despite the absent grammar rule.
  • [high] The Tree-sitter grammar still models try only as a statement and cannot parse the documented try-expression form. examples/syntax/try_expression.lk contains let recovered = try { checked_div(1, 0) } catch e { -1.0 };, but _expression has no try-expression alternative and the only rule is try_statement, which is listed under _statement; the assignment RHS expects _full_expression, so this source is necessarily recovered as an error rather than a try expression. Thus editor parsing/diagnostics do not recognize the same try surface as the compiler. This is introduced by adding the try surface without adding its expression grammar; it would be false only if the generated parser accepted that RHS as a named, non-error try-expression node through some other rule.
  • [medium] The ASan lkrt correctness workflow does not use the same-toolchain lk-api-cabi archive that scripts/build_lkrt_asan.sh builds. The script explicitly emits LK_API_STATICLIB for the nightly-built API archive and explains that hybrid programs must link lkrt and lk-api built by the same toolchain, but the workflow's test command sets only LKRT_STATICLIB=... and omits LK_API_STATICLIB. Consequently the advertised ASan-lkrt differential run either links the separately prebuilt stable API archive or fails/loses the intended toolchain pairing, so it does not exercise the configured sanitizer deployment as described. This would be disproved if the native compile pipeline always discovers and selects the script's API archive from LKRT_STATICLIB alone, without consulting LK_API_STATICLIB.
♻️ Previously reported (still present) (27)
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.
  • [high] Dependency form-data@4.0.5 is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.
  • [high] Dependency js-yaml@4.1.1 is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the mailto: validator scan-loop on attacker text); upgrade to at least 5.0.2.
  • [high] Dependency tmp@0.2.5 is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.
  • [medium] Dependency js-yaml@4.1.1 is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.
  • [medium] Dependency markdown-it@14.1.1 is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.
  • [medium] Dependency qs@6.15.1 is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
  • [info] Dependency memmap2@0.2.3 is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate memmap2); upgrade to at least 0.9.11.
  • [info] Dependency bare-metal@0.2.5 is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
🗑️ Suppressed and duplicate diagnostics (1)
  • The VS Code TextMate grammar does not classify defer as a control keyword, although the compiler now reserves it and the documentation/examples present it as syntax. The #keywords control regex includes try|catch and concurrency/control words but omits defer, so defer note(...) is highlighted as an ordinary identifier/call rather than a keyword. The editor grammar test only checks type-name parity and cannot catch this keyword drift. This is introduced by the expanded syntax update; it would be false only if a higher-priority TextMate pattern outside #keywords classified the defer token as a keyword (none is present in the supplied grammar).
🤖 Prompt for AI agents — all findings (44)
In core/src/stmt/defer.rs around line 104, address this finding:
A top-level `defer` is silently accepted and rewritten instead of being rejected as an unsupported placement.

In core/src/typ/stdlib_sig.rs around line 78, address this finding:
Generated stdlib calls reject valid calls that omit parameters having defaults because `required_params` counts every non-`optional` parameter as required, ignoring `has_default`.

In core/src/typ/type_checker/expressions/stdlib.rs, address this finding:
Stdlib signature checking skips type validation for supplied optional parameters, allowing arguments of incompatible types through the checker.

In core/src/val/runtime_model/equality.rs around line 163, address this finding:
Deep or cyclic object values can still recurse without the new depth bound during equality.

In aot/codegen/src/clif.rs around line 896, address this finding:
Statement-form try regions can pass a Bool input with an invalid machine-word representation. `TryRegionCall` stores each argument directly into an 8-byte slot, so an `I8` Bool writes only one byte while `lkrt_rt_try_region` reads a `long long`; the remaining bytes are uninitialized stack data. A function such as `fn f(b) { try { if b { ... } } catch e { ... } }` can therefore observe a nondeterministic/non-0-or-1 value (or take the wrong branch), despite Bool being explicitly admitted as a crossing word. This is introduced by the new Bool try-boundary support; it would be disproved only if Cranelift's `stack_store` widened I8 to an initialized 8-byte slot, which its typed store does not do.

In aot/lower/src/function.rs around line 245, address this finding:
The try-region arity check omits the two return-channel cells for a body containing an enclosing-function `return`. `function.rs` accepts `try_body_params + try_body_cells <= 8`, then unconditionally appends two more cells for the return flag/value. A region with seven crossing input/cell words and a return consequently emits a 9-argument body call, while `lkrt_rt_try_region` only has cases 0..8 and traps in its default (and the native lowering does not reject it). Thus a supported VM program can compile to a native binary that traps rather than taking its return path. This is introduced by the new return-channel outlining; it would be false only if those two channel cells were not actually appended or the trampoline supported more than eight arguments.

In aot/codegen/src/clif.rs around line 356, address this finding:
Hybrid bridge calls use one module-global argument buffer for all native call sites. `lk_hybrid_argbuf` is a single shared writable data object, and `call_vm` writes tags/payloads into it before invoking the VM bridge; there is no lock, per-call stack buffer, or thread-local storage. If two native threads concurrently execute hybrid calls (for example, native concurrency/spawn code reaches VM-executed functions), one call can overwrite another's arguments between its writes and `lk_hybrid_call_*`, causing the VM function to receive mixed arguments. The claim would be disproved only if hybrid execution were provably single-threaded for the lifetime of every generated module; the surrounding native pipeline exposes concurrent execution, so the code's explicit single-threaded assumption is not a safe invariant.

In lkrt/src/system.rs around line 82, address this finding:
The native runtime exposes privileged system-control ABI functions in the hosted `std` profile, so a program that calls `cpu_load_idt`/the other system helpers on a normal host executes `lidt`/`lgdt`/`mov cr*` instead of receiving the VM's documented bare-metal refusal. For example, `lkrt_cpu_load_idt` has an x86 implementation with no `feature = "std"` guard, and `lkrt/src/lib.rs` re-exports it unconditionally; on hosted x86_64 this can fault or terminate the process rather than produce a catchable refusal. The VM counterpart is compiled only under `not(feature = "std")` and otherwise returns `cpu_load_idt requires bare-metal execution on x86-64`. This capability exposure was introduced by registering/exporting the new system ABI family; it would be disproven if hosted AOT lowering could prove these symbols unreachable or if the std implementation itself rejected rather than executing the privileged instruction.

In lkrt/src/lkstr.rs around line 330, address this finding:
`lkrt_str_repeat` silently returns an empty string for negative counts, while the VM/core string method rejects them with `string.repeat() count must be non-negative, got {n}`. A native program such as `"x".repeat(-1)` therefore succeeds with `""` (or a module call lowered to this symbol), whereas the VM raises a catchable error; this also bypasses the stdlib's documented count validation. The changed native helper explicitly implements `if n <= 0 { return empty }`, so this is a concrete host/native parity and invalid-input failure, not merely missing validation. It would be disproven if all native call sites statically exclude negative values or if the language contract intentionally changed negative repeat to empty in the same revision.

In bare-metal-x86/src/user.rs around line 62, address this finding:
The ring-3 syscall trampoline enters the compiled dispatcher with a misaligned stack: after the CPU's ring transition, the seven pushes leave RSP 16-byte aligned, but `sub rsp, 264` changes it to RSP%16 == 8 immediately before `call lk_syscall_dispatch` (the ABI requires the caller's RSP to be 0 mod 16 before a call). A dispatcher or callee that emits an aligned SSE stack spill can fault or misbehave on every syscall, so the user boundary is not ABI-correct; reserving 256 bytes (or otherwise explicitly aligning) would preserve the required phase. This is introduced by the new syscall trampoline; it would be disproved only if the actual native LK calling convention explicitly requires the opposite stack phase and all possible callees avoid alignment-sensitive spills.

In bare-metal-x86/program.lk around line 2811, address this finding:
`build_user_space` leaks already allocated page-table pages when any later allocation fails: it performs four `page_alloc` calls and immediately returns 0 if any is zero, without releasing the nonzero pages. Under page pressure (or after an allocator failure), repeated user-task/address-space creation permanently consumes up to three pages per failed attempt, and the caller does not roll those pages back either. This violates the resource-lifetime obligation on failure paths and can turn a recoverable temporary exhaustion into permanent inability to create tasks; the claim would be false only if page_alloc were guaranteed all-or-nothing/reserving four pages (it is not) or another caller reliably reclaimed the partial allocations.

In bare-metal-x86/program.lk around line 3003, address this finding:
Dynamic interrupt installation is not race-safe across the IDT gate and runtime handler table. `install_device_handler` writes a present gate first, then writes `lkrt_isr_handlers[vector]`; with interrupts already enabled at this point, an interrupt for a vector whose line is currently unmasked can arrive in that interval. The generic ISR sees the still-zero handler, skips the call and returns with `iretq` without the device driver's EOI, leaving the PIC line in service (and potentially losing/stalling that device permanently). The driver comments rely on unmasking last, but the helper itself does not mask/disable interrupts, and it is also reused for vectors that may already be installed. This would be disproved only if every caller could prove the line is masked and cannot have a pending delivery for the entire gate/table update, an invariant the exported helper does not enforce.

In cli/src/main.rs, address this finding:
The Tier 0 bundle path cannot execute a bundled source file that contains a relative file import.

In cli/src/main.rs around line 1486, address this finding:
File bundling drops imported struct declarations (and trait declarations) from the merged artifact, so imported constructors/type identity are not preserved for native lowering.

In ecosystem/tree-sitter-lk/grammar.js around line 476, address this finding:
The committed Tree-sitter grammar does not recognize the newly documented `defer` statement. `defer` is a lexer keyword and is used throughout `examples/syntax/defer.lk`, but `_statement` has no defer rule and there is no `defer` rule anywhere in `grammar.js`; the generated grammar/parser artifacts consequently cannot produce a defer node and will recover with an error around each `defer`. This makes Tree-sitter parsing and editor grammar behavior disagree with the compiler for a documented construct. This is introduced by the scope's expanded-language grammar update; it would be disproved only if the generated parser accepted `defer ...;` as a named non-error construct despite the absent grammar rule.

In ecosystem/tree-sitter-lk/grammar.js around line 788, address this finding:
The Tree-sitter grammar still models `try` only as a statement and cannot parse the documented try-expression form. `examples/syntax/try_expression.lk` contains `let recovered = try { checked_div(1, 0) } catch e { -1.0 };`, but `_expression` has no try-expression alternative and the only rule is `try_statement`, which is listed under `_statement`; the assignment RHS expects `_full_expression`, so this source is necessarily recovered as an error rather than a try expression. Thus editor parsing/diagnostics do not recognize the same try surface as the compiler. This is introduced by adding the try surface without adding its expression grammar; it would be false only if the generated parser accepted that RHS as a named, non-error try-expression node through some other rule.

In .github/workflows/correctness.yml around line 97, address this finding:
The ASan lkrt correctness workflow does not use the same-toolchain `lk-api-cabi` archive that `scripts/build_lkrt_asan.sh` builds. The script explicitly emits `LK_API_STATICLIB` for the nightly-built API archive and explains that hybrid programs must link lkrt and lk-api built by the same toolchain, but the workflow's test command sets only `LKRT_STATICLIB=...` and omits `LK_API_STATICLIB`. Consequently the advertised ASan-lkrt differential run either links the separately prebuilt stable API archive or fails/loses the intended toolchain pairing, so it does not exercise the configured sanitizer deployment as described. This would be disproved if the native compile pipeline always discovers and selects the script's API archive from `LKRT_STATICLIB` alone, without consulting `LK_API_STATICLIB`.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `form-data@4.0.5` is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the `mailto:` validator scan-loop on attacker text); upgrade to at least 5.0.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `tmp@0.2.5` is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `markdown-it@14.1.1` is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `qs@6.15.1` is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.

In Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.

In Cargo.lock, address this finding:
Dependency `memmap2@0.2.3` is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate `memmap2`); upgrade to at least 0.9.11.

In bare-metal/Cargo.lock, address this finding:
Dependency `bare-metal@0.2.5` is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.

In ecosystem/zed-ext/Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.
📜 Review details

Coverage

  • scopes: 5/7 complete

Comment thread lkrt/src/system.rs
/// as it is loaded. A malformed gate is not a fault the kernel can report — the
/// CPU triple-faults trying to report it, and the machine resets.
#[unsafe(no_mangle)]
pub extern "C" fn lkrt_cpu_load_idt(base: i64, limit: i64) {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security | 🟡 Medium

The native runtime exposes privileged system-control ABI functions in the hosted std profile, so a program that calls cpu_load_idt/the other system helpers on a normal host executes lidt/lgdt/mov cr* instead of receiving the VM's documented bare-metal refusal. For example, lkrt_cpu_load_idt has an x86 implementation with no feature = "std" guard, and lkrt/src/lib.rs re-exports it unconditionally; on hosted x86_64 this can fault or terminate the process rather than produce a catchable refusal. The VM counterpart is compiled only under not(feature = "std") and otherwise returns cpu_load_idt requires bare-metal execution on x86-64. This capability exposure was introduced by registering/exporting the new system ABI family; it would be disproven if hosted AOT lowering could prove these symbols unreachable or if the std implementation itself rejected rather than executing the privileged instruction.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ The exact host signal/termination status is OS-dependent, but the x86_64 hosted path necessarily executes a privileged lidt instruction at user privilege and cannot produce the VM's catchable refusal.
🤖 Prompt for AI agents
In lkrt/src/system.rs, address this finding:
The native runtime exposes privileged system-control ABI functions in the hosted `std` profile, so a program that calls `cpu_load_idt`/the other system helpers on a normal host executes `lidt`/`lgdt`/`mov cr*` instead of receiving the VM's documented bare-metal refusal. For example, `lkrt_cpu_load_idt` has an x86 implementation with no `feature = "std"` guard, and `lkrt/src/lib.rs` re-exports it unconditionally; on hosted x86_64 this can fault or terminate the process rather than produce a catchable refusal. The VM counterpart is compiled only under `not(feature = "std")` and otherwise returns `cpu_load_idt requires bare-metal execution on x86-64`. This capability exposure was introduced by registering/exporting the new system ABI family; it would be disproven if hosted AOT lowering could prove these symbols unreachable or if the std implementation itself rejected rather than executing the privileged instruction.

To have the bot fix this, comment @winnowl fix.

// The CPU aligns RSP to 16 on the way in and seven pushes leave it eight
// off, so the 264 both reserves the area and restores the alignment `call`
// expects.
" sub rsp, 264",

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟠 High

The ring-3 syscall trampoline enters the compiled dispatcher with a misaligned stack: after the CPU's ring transition, the seven pushes leave RSP 16-byte aligned, but sub rsp, 264 changes it to RSP%16 == 8 immediately before call lk_syscall_dispatch (the ABI requires the caller's RSP to be 0 mod 16 before a call). A dispatcher or callee that emits an aligned SSE stack spill can fault or misbehave on every syscall, so the user boundary is not ABI-correct; reserving 256 bytes (or otherwise explicitly aligning) would preserve the required phase. This is introduced by the new syscall trampoline; it would be disproved only if the actual native LK calling convention explicitly requires the opposite stack phase and all possible callees avoid alignment-sensitive spills.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ The currently generated syscall dispatcher may not happen to emit an alignment-sensitive movaps spill, so an immediate crash on every present syscall is not proven; the trampoline nevertheless violates the SysV call-site contract and can fail when the dispatcher or any reachable callee uses aligned stack accesses. The candidate's statement that the CPU itself aligns the post-transition RSP is inaccurate: with the 16-byte-aligned TSS stack top, the CPU's five-word privilege-transition frame leaves RSP%16==8, then seven pushes make it 0, and sub rsp,264 makes it 8 before call.
🤖 Prompt for AI agents
In bare-metal-x86/src/user.rs, address this finding:
The ring-3 syscall trampoline enters the compiled dispatcher with a misaligned stack: after the CPU's ring transition, the seven pushes leave RSP 16-byte aligned, but `sub rsp, 264` changes it to RSP%16 == 8 immediately before `call lk_syscall_dispatch` (the ABI requires the caller's RSP to be 0 mod 16 before a call). A dispatcher or callee that emits an aligned SSE stack spill can fault or misbehave on every syscall, so the user boundary is not ABI-correct; reserving 256 bytes (or otherwise explicitly aligning) would preserve the required phase. This is introduced by the new syscall trampoline; it would be disproved only if the actual native LK calling convention explicitly requires the opposite stack phase and all possible callees avoid alignment-sensitive spills.

To have the bot fix this, comment @winnowl fix.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🔎 Confirmed findings (3)
  • high defer inside if let/while let is neither rejected nor rewritten, so a deferred statement can reach downstream passes and returns in those bodies do not release pending resources. (inline)
  • medium build_user_space leaks page-table pages whenever any of its four allocations fails. (inline)
  • medium User address-space construction has no rollback for partial allocation: if page_alloc returns zero for any of pml4, pdpt, directory, or table, build_user_space returns zero while retaining all earlier pages. The boot path calls this twice and never frees a failed space, so a memory-pressure boot can consume up to three pages per failed attempt and then report missing tasks with those pages permanently unavailable. (inline)

⛔ Unresolved from previous review (9) — not approved until fixed

  • The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.
  • aot/lower/src/function.rs: return; inside an outlined try body does not propagate the enclosing-function return channel. The region scanner marks Opcode::Return0 as body_returns and the caller allocates/passes flag and value cells, but the body lowering only parks a return when matching Some(Exit::Ret(Some(reg))); Exit::Ret(None) bypasses that code and leaves the flag at its seeded false value. On the normal body-completion edge the caller therefore treats return; as a raised/catch outcome (or, for an all-return body, reaches the wrong continuation) instead of returning nil from the enclosing function. A minimal failure is try { return; } catch e { print("caught") } in a non-entry function: the VM returns immediately, while native lowering enters the catch path. This would be disproved if Return0 were converted to an Exit::Ret(Some(...)) before this match or if another lowering path sets the return flag for Ret(None), neither of which is present in the inspected code.
  • The Tier-1 hybrid bridge uses one module-global lk_hybrid_argbuf for every CallVm site, but the native runtime also supports concurrent spawned tasks. If two native threads execute a bridged call concurrently, each overwrites the shared tags/payloads before the other bridge reads them, so the VM function can receive another thread's arguments (or inconsistent tag/value pairs). The comment's single-threaded assumption is not enforced by the language/runtime.
  • cli/src/native_compile.rs: Native executable cache entries are not invalidated when the linked lkrt/runtime or toolchain changes, allowing an executable with an old ABI or runtime implementation to be reused for unchanged source.
  • aot/codegen/src/clif.rs: Tier 1 hybrid calls are not safe when native code executes concurrently: all generated CallVm sites share one writable global argument buffer, so threads can overwrite each other's tagged arguments before the bridge reads them.
  • core/src/val/runtime_model/equality.rs: Structural equality of nested objects bypasses the depth guard and can recurse until Rust stack overflow.
  • aot/codegen/src/clif.rs: The Tier-1 hybrid argument buffer is a single mutable module-global, but native spawn/go execute compiled callbacks on OS threads. Two concurrent hybrid calls (or a callback racing the parent) overwrite tags/values between marshaling and lk_hybrid_call_*, causing the VM to receive another call's arguments.
  • The native dynamic-value work adds Set and Bytes carrier tags (and documents them as values that can cross a bridged return), but the hybrid VM bridge still only marshals Nil/Bool/Int/Float/Str/List/Map: any VM function returning a Set or Bytes reaches the Some(other) => hybrid_die(...) arm and terminates instead of returning the new LkDyn. The public header likewise advertises tags only through MAP, so a Tier-1 hybrid caller cannot consume these values despite the runtime/ABI supporting them.
  • bare-metal-x86/src/user.rs: The ring-3 syscall trampoline enters the compiled dispatcher with a misaligned stack: after the CPU's ring transition, the seven pushes leave RSP 16-byte aligned, but sub rsp, 264 changes it to RSP%16 == 8 immediately before call lk_syscall_dispatch (the ABI requires the caller's RSP to be 0 mod 16 before a call). A dispatcher or callee that emits an aligned SSE stack spill can fault or misbehave on every syscall, so the user boundary is not ABI-correct; reserving 256 bytes (or otherwise explicitly aligning) would preserve the required phase. This is introduced by the new syscall trampoline; it would be disproved only if the actual native LK calling convention explicitly requires the opposite stack phase and all possible callees avoid alignment-sensitive spills.
🧹 Additional findings from this change (not shown inline) (15)
  • [high] A defer wrapped in an attribute at the direct function-body level is left as Stmt::Defer instead of being consumed or rejected. rewrite_sequence only recognizes a direct Stmt::Defer; descend unwraps Attributed but does not call reject_stray on the unwrapped item, so #[attr] defer cleanup(); survives into type checking/compilation despite the stated invariant that downstream never sees Defer.
  • [high] Trait default method bodies are skipped by defer desugaring, then copied into impls afterward, allowing Stmt::Defer to be emitted in every implementing method. descend has no Stmt::Trait arm and apply_trait_defaults clones default_methods after desugar_defers has completed.
  • [medium] Declared optional stdlib parameters are treated as completely unconstrained when supplied, so calls with the wrong concrete type pass type checking despite the generated signature and runtime contract declaring a type.
  • [medium] A local value shadowing a stdlib module is still checked against the stdlib callable signature, so a dotted field call can be rejected (or given the stdlib return type) even though it refers to the local value.
  • [medium] The signature and global-arity registries are process-global and never scoped to a ModuleRegistry or reset, so a checker created later for a bare/no-stdlib or different module set inherits declarations from an earlier stdlib registration.
  • [high] A return; inside an outlined protected body does not propagate the enclosing-function return channel. shape_at marks Return0 as body_returns, so the parent allocates flag/value cells and the body is lowered with those parameters, but the terminator lowering only parks a return in the Some(Exit::Ret(Some(reg))) arm (and a separate Dyn-return fallback); Exit::Ret(None) falls through without setting the flag. Consequently try { return; } catch { ... } is treated as a normal successful try with a false flag and continues after the region instead of returning from the enclosing function, diverging from VM semantics.
  • [high] Native TryRegionCall does not marshal the newly accepted F64 and Bool region-boundary arguments as eight-byte words. The lowering explicitly permits both in crosses_as_word, and F64 body parameters are declared as I64 and bit-cast at entry, but Cranelift codegen writes self.v(arg) directly to the i64 argument stack buffer. For F64 this is an F64 store into an integer-word buffer (a verifier/type failure or wrong ABI lowering); for Bool it stores only an I8 while the C trampoline reads a full long long, leaving the remaining bytes unspecified. A function with a try body reading an enclosing float/bool parameter therefore either falls back/fails codegen or receives corrupted bits, rather than VM-equivalent values.
  • [high] The protected-region arity check omits the two return-channel cells. lower_function rejects only cells.len() + try_body_params.len() &gt; 8, then later appends flag and value cells whenever try_body_returns is set. A valid shape with seven ordinary crossing values and a body return reaches TryRegionCall with nine arguments; the C trampoline's arity switch has cases only through 8 and executes __builtin_trap() for 9. This is a native crash rather than an intentional rejection/fallback.
  • [medium] Release/profile native linking can silently select a debug lkrt archive instead of the requested release artifact.
  • [high] Bare-metal ISR handlers do not receive the interrupt vector advertised by the ABI/comments. In __lkrt_isr_common, the vector is loaded into rax only to fetch the handler, and the handler is then invoked with call rax without moving the vector into the SysV first argument register (rdi). A handler servicing multiple vectors therefore observes an unrelated/stale rdi value (or whatever the interrupted code had), so vector dispatch logic can act on the wrong device/vector. This is introduced by the new common ISR implementation; it would be disproven only if the generated handler ABI intentionally takes no vector argument and all callers ignore the documented vector, contrary to the code's stated calling convention.
  • [medium] time.after/time.timeout leak a task registry entry and its JoinHandle on every timer invocation. spawn_timer calls register_task(std::thread::spawn(...)), but returns the channel id rather than the task id; the timer's task is therefore never awaitable, and no cleanup path removes it from tasks(). A program repeatedly arming timers accumulates completed TaskSlots (and the associated join-handle allocations) for its entire process lifetime, violating the runtime resource-lifetime requirement. This would be false only if some separate cleanup path iterated and removes completed timer tasks, but tasks() is only consumed by lkrt_task_await, which cannot be called with these hidden task ids.
  • [medium] The startup heap reservation leaks every page already allocated when the 16-page reservation is incomplete or non-contiguous: the loop keeps allocating after heap_base is zero or a later allocation fails, sets heap_pages_ok false, and calls heap_init(..., 0, 0) without releasing the pages. On a low-memory boot this permanently reduces the page allocator and can make subsequent task/address-space allocations fail despite the heap being disabled.
  • [high] Native execution can reuse stale cached binaries after an imported module changes.
  • [medium] Zed can select a non-executable lk-lsp file and then fail to start the language server.
  • [medium] The AOT lowering and the shared error runtime disagree on the callable contract: the stdlib registers error as variadic and language::error explicitly accepts zero or multiple arguments, but Builtin::ErrorRaise rejects every native call whose argc is not exactly one. Thus valid error()/error(a, b) programs take a different backend path (or lose native compilation) than the VM/shared host implementation, and the ABI/native path cannot preserve the published variadic behavior.
♻️ Previously reported (still present) (27)
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.
  • [high] Dependency form-data@4.0.5 is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.
  • [high] Dependency js-yaml@4.1.1 is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the mailto: validator scan-loop on attacker text); upgrade to at least 5.0.2.
  • [high] Dependency tmp@0.2.5 is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.
  • [medium] Dependency js-yaml@4.1.1 is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.
  • [medium] Dependency markdown-it@14.1.1 is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.
  • [medium] Dependency qs@6.15.1 is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
  • [info] Dependency memmap2@0.2.3 is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate memmap2); upgrade to at least 0.9.11.
  • [info] Dependency bare-metal@0.2.5 is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
🤖 Prompt for AI agents — all findings (45)
In core/src/stmt/defer.rs around line 134, address this finding:
`defer` inside `if let`/`while let` is neither rejected nor rewritten, so a deferred statement can reach downstream passes and returns in those bodies do not release pending resources.

In core/src/stmt/defer.rs around line 120, address this finding:
A defer wrapped in an attribute at the direct function-body level is left as `Stmt::Defer` instead of being consumed or rejected. `rewrite_sequence` only recognizes a direct `Stmt::Defer`; `descend` unwraps `Attributed` but does not call `reject_stray` on the unwrapped item, so `#[attr] defer cleanup();` survives into type checking/compilation despite the stated invariant that downstream never sees Defer.

In core/src/stmt/defer.rs around line 144, address this finding:
Trait default method bodies are skipped by defer desugaring, then copied into impls afterward, allowing `Stmt::Defer` to be emitted in every implementing method. `descend` has no `Stmt::Trait` arm and `apply_trait_defaults` clones `default_methods` after `desugar_defers` has completed.

In core/src/typ/type_checker/expressions/stdlib.rs, address this finding:
Declared optional stdlib parameters are treated as completely unconstrained when supplied, so calls with the wrong concrete type pass type checking despite the generated signature and runtime contract declaring a type.

In core/src/typ/type_checker/expressions/stdlib.rs, address this finding:
A local value shadowing a stdlib module is still checked against the stdlib callable signature, so a dotted field call can be rejected (or given the stdlib return type) even though it refers to the local value.

In core/src/typ/stdlib_sig.rs around line 100, address this finding:
The signature and global-arity registries are process-global and never scoped to a ModuleRegistry or reset, so a checker created later for a bare/no-stdlib or different module set inherits declarations from an earlier stdlib registration.

In aot/lower/src/function.rs around line 631, address this finding:
A `return;` inside an outlined protected body does not propagate the enclosing-function return channel. `shape_at` marks `Return0` as `body_returns`, so the parent allocates flag/value cells and the body is lowered with those parameters, but the terminator lowering only parks a return in the `Some(Exit::Ret(Some(reg)))` arm (and a separate Dyn-return fallback); `Exit::Ret(None)` falls through without setting the flag. Consequently `try { return; } catch { ... }` is treated as a normal successful try with a false flag and continues after the region instead of returning from the enclosing function, diverging from VM semantics.

In aot/codegen/src/clif.rs around line 896, address this finding:
Native `TryRegionCall` does not marshal the newly accepted `F64` and `Bool` region-boundary arguments as eight-byte words. The lowering explicitly permits both in `crosses_as_word`, and F64 body parameters are declared as `I64` and bit-cast at entry, but Cranelift codegen writes `self.v(arg)` directly to the `i64` argument stack buffer. For F64 this is an F64 store into an integer-word buffer (a verifier/type failure or wrong ABI lowering); for Bool it stores only an I8 while the C trampoline reads a full `long long`, leaving the remaining bytes unspecified. A function with a try body reading an enclosing float/bool parameter therefore either falls back/fails codegen or receives corrupted bits, rather than VM-equivalent values.

In aot/lower/src/function.rs around line 245, address this finding:
The protected-region arity check omits the two return-channel cells. `lower_function` rejects only `cells.len() + try_body_params.len() > 8`, then later appends `flag` and `value` cells whenever `try_body_returns` is set. A valid shape with seven ordinary crossing values and a body `return` reaches `TryRegionCall` with nine arguments; the C trampoline's arity switch has cases only through 8 and executes `__builtin_trap()` for 9. This is a native crash rather than an intentional rejection/fallback.

In aot/driver/src/native_executable.rs around line 257, address this finding:
Release/profile native linking can silently select a debug `lkrt` archive instead of the requested release artifact.

In lkrt/src/isr.rs around line 107, address this finding:
Bare-metal ISR handlers do not receive the interrupt vector advertised by the ABI/comments. In `__lkrt_isr_common`, the vector is loaded into `rax` only to fetch the handler, and the handler is then invoked with `call rax` without moving the vector into the SysV first argument register (`rdi`). A handler servicing multiple vectors therefore observes an unrelated/stale `rdi` value (or whatever the interrupted code had), so vector dispatch logic can act on the wrong device/vector. This is introduced by the new common ISR implementation; it would be disproven only if the generated handler ABI intentionally takes no vector argument and all callers ignore the documented vector, contrary to the code's stated calling convention.

In lkrt/src/chan.rs around line 291, address this finding:
`time.after`/`time.timeout` leak a task registry entry and its `JoinHandle` on every timer invocation. `spawn_timer` calls `register_task(std::thread::spawn(...))`, but returns the channel id rather than the task id; the timer's task is therefore never awaitable, and no cleanup path removes it from `tasks()`. A program repeatedly arming timers accumulates completed `TaskSlot`s (and the associated join-handle allocations) for its entire process lifetime, violating the runtime resource-lifetime requirement. This would be false only if some separate cleanup path iterated and removes completed timer tasks, but `tasks()` is only consumed by `lkrt_task_await`, which cannot be called with these hidden task ids.

In bare-metal-x86/program.lk around line 2807, address this finding:
`build_user_space` leaks page-table pages whenever any of its four allocations fails.

In bare-metal-x86/program.lk around line 3864, address this finding:
The startup heap reservation leaks every page already allocated when the 16-page reservation is incomplete or non-contiguous: the loop keeps allocating after `heap_base` is zero or a later allocation fails, sets `heap_pages_ok` false, and calls `heap_init(..., 0, 0)` without releasing the pages. On a low-memory boot this permanently reduces the page allocator and can make subsequent task/address-space allocations fail despite the heap being disabled.

In bare-metal-x86/program.lk around line 2811, address this finding:
User address-space construction has no rollback for partial allocation: if `page_alloc` returns zero for any of `pml4`, `pdpt`, `directory`, or `table`, `build_user_space` returns zero while retaining all earlier pages. The boot path calls this twice and never frees a failed space, so a memory-pressure boot can consume up to three pages per failed attempt and then report missing tasks with those pages permanently unavailable.

In cli/src/native_compile.rs, address this finding:
Native execution can reuse stale cached binaries after an imported module changes.

In ecosystem/zed-ext/src/lib.rs around line 103, address this finding:
Zed can select a non-executable `lk-lsp` file and then fail to start the language server.

In aot/lower/src/lower_builtin.rs, address this finding:
The AOT lowering and the shared `error` runtime disagree on the callable contract: the stdlib registers `error` as variadic and `language::error` explicitly accepts zero or multiple arguments, but `Builtin::ErrorRaise` rejects every native call whose argc is not exactly one. Thus valid `error()`/`error(a, b)` programs take a different backend path (or lose native compilation) than the VM/shared host implementation, and the ABI/native path cannot preserve the published variadic behavior.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `form-data@4.0.5` is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the `mailto:` validator scan-loop on attacker text); upgrade to at least 5.0.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `tmp@0.2.5` is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `markdown-it@14.1.1` is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `qs@6.15.1` is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.

In Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.

In Cargo.lock, address this finding:
Dependency `memmap2@0.2.3` is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate `memmap2`); upgrade to at least 0.9.11.

In bare-metal/Cargo.lock, address this finding:
Dependency `bare-metal@0.2.5` is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.

In ecosystem/zed-ext/Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.
📜 Review details

Coverage

  • scopes: 7/9 complete

Comment thread core/src/stmt/defer.rs
}
Ok(())
}
Stmt::If {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟠 High

defer inside if let/while let is neither rejected nor rewritten, so a deferred statement can reach downstream passes and returns in those bodies do not release pending resources.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In core/src/stmt/defer.rs, address this finding:
`defer` inside `if let`/`while let` is neither rejected nor rewritten, so a deferred statement can reach downstream passes and returns in those bodies do not release pending resources.

To have the bot fix this, comment @winnowl fix.

Comment thread bare-metal-x86/program.lk
// Returns 0 when there are no pages left. Zero is unambiguous as a CR3: page 0
// is not in any range this allocator hands out.
fn build_user_space(stack_physical: Int) -> Int {
let pml4 = page_alloc(SHARED_PAGES);

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Resource Lifetime | 🟡 Medium

build_user_space leaks page-table pages whenever any of its four allocations fails.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ The exact baseline implementation is not available, so introduction is inferred from the changed build_user_space implementation and its new allocator-backed address-space construction.
🤖 Prompt for AI agents
In bare-metal-x86/program.lk, address this finding:
`build_user_space` leaks page-table pages whenever any of its four allocations fails.

To have the bot fix this, comment @winnowl fix.

Comment thread bare-metal-x86/program.lk
let pdpt = page_alloc(SHARED_PAGES);
let directory = page_alloc(SHARED_PAGES);
let table = page_alloc(SHARED_PAGES);
if (pml4 == 0 || pdpt == 0 || directory == 0 || table == 0) {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Resource Lifetime | 🟡 Medium

User address-space construction has no rollback for partial allocation: if page_alloc returns zero for any of pml4, pdpt, directory, or table, build_user_space returns zero while retaining all earlier pages. The boot path calls this twice and never frees a failed space, so a memory-pressure boot can consume up to three pages per failed attempt and then report missing tasks with those pages permanently unavailable.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In bare-metal-x86/program.lk, address this finding:
User address-space construction has no rollback for partial allocation: if `page_alloc` returns zero for any of `pml4`, `pdpt`, `directory`, or `table`, `build_user_space` returns zero while retaining all earlier pages. The boot path calls this twice and never frees a failed space, so a memory-pressure boot can consume up to three pages per failed attempt and then report missing tasks with those pages permanently unavailable.

To have the bot fix this, comment @winnowl fix.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🔎 Confirmed findings (4)
  • high Structural equality does not enforce MAX_VALUE_DEPTH for object-field recursion, so deeply nested or cyclic object graphs can overflow the Rust stack instead of returning the documented comparison error. (inline)
  • high A try body that returns can exceed the trampoline's fixed arity even though lowering accepts it, causing the generated native program to trap instead of executing the VM semantics. (inline)
  • medium Failed user-task/address-space creation leaks physical pages and permanently reduces allocator capacity. (inline)
  • medium The multiboot header does not request a memory map, but the kernel unconditionally initializes its page allocator from the memory-map API; under a compliant multiboot loader entry_count() is therefore zero, so usable_length is zero and all task/user/heap page allocations fail. (inline)

⛔ Unresolved from previous review (4) — not approved until fixed

  • The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.
  • The Tier-1 hybrid bridge uses one module-global lk_hybrid_argbuf for every CallVm site, but the native runtime also supports concurrent spawned tasks. If two native threads execute a bridged call concurrently, each overwrites the shared tags/payloads before the other bridge reads them, so the VM function can receive another thread's arguments (or inconsistent tag/value pairs). The comment's single-threaded assumption is not enforced by the language/runtime.
  • The native dynamic-value work adds Set and Bytes carrier tags (and documents them as values that can cross a bridged return), but the hybrid VM bridge still only marshals Nil/Bool/Int/Float/Str/List/Map: any VM function returning a Set or Bytes reaches the Some(other) => hybrid_die(...) arm and terminates instead of returning the new LkDyn. The public header likewise advertises tags only through MAP, so a Tier-1 hybrid caller cannot consume these values despite the runtime/ABI supporting them.
  • Native execution can reuse stale cached binaries after an imported module changes.
🧹 Additional findings from this change (not shown inline) (10)
  • [medium] The checked-in C embedding example is now unusable because it still tells consumers to link -llk_api, but the api crate no longer emits liblk_api.a; only the separately named liblk_api_cabi.a is produced. Following the documented command on a fresh checkout therefore fails to find the archive (and does not enable the ffi feature).
  • [medium] Hybrid native compilation cannot locate the split API archive on Windows because ensure_lk_api_staticlib unconditionally returns target/release/liblk_api_cabi.a; Rust staticlib output on Windows uses the .lib filename (as the neighboring lkrt lookup already accounts for with lkrt_cabi.lib). Any Windows program that reaches the Tier 1 bridge therefore fails the link with a missing input archive even after the lk-api-cabi build succeeds.
  • [medium] The Tier 1 link command leaves the known duplicate-symbol problem unsolved on macOS: both lkrt and lk-api archives contain common dependency objects, and the implementation only adds -Wl,--allow-multiple-definition when !cfg!(target_os = "macos"). On macOS the command instead passes both archives without an ld64 equivalent, so hybrid programs that require both archives can fail with duplicate definitions (or become dependent on archive extraction order), despite macOS being treated as a supported native platform by the surrounding framework/link flags.
  • [medium] The startup heap reservation consumes pages without returning them when the reservation is incomplete, so a low-memory boot loses every successfully allocated heap page even though it installs an empty heap.
  • [medium] Tree-sitter does not accept the canonical try expression form introduced by this change.
  • [high] Tree-sitter grammar rejects the canonical defer statement, so valid deferred code is shown as syntax errors in editor integrations.
  • [medium] Tree-sitter macro groups cannot parse canonical internal macro rules using @ (and therefore cannot parse the new recursive macro example).
  • [medium] The published website stdlib reference is not synchronized with the changed runtime/stdlib surface: it still documents chan.recv as returning [ok, value] and lists only task.spawn/task.await, while the implementation and the updated docs/concurrency.md say recv returns the value and raises after close, and task also exposes try_await, join_all, and sleep (including list/variadic join_all). It also omits encoding.json/yaml/toml.stringify, despite those exports being present in stdlib/crates/encoding and covered by the AOT differential tests. A user following the website can destructure a non-existent status pair or miss the serializer APIs, so the public compatibility claim is false.
  • [medium] The AOT gap document still presents retired/nonexistent LLVM implementation paths as if they describe the current backend. For example it says the current entry point is compile_native_scalar_main_artifact under llvm/src/llvm/backend.rs, refers readers to llvm/src/llvm/diagnostics.rs and llvm/src/llvm/dynamic_containers/, and gives cargo test -p lk-llvm as validation, but this scope has moved the implementation into aot/lower, aot/codegen, and aot/driver (and there is no lk-llvm workspace package). A maintainer using the document to investigate an AOT gap will be sent to paths that cannot be opened and a package command that cannot run, so the documented AOT limitations and verification procedure do not match the actual compiler/runtime platform.
  • [high] The sandboxed VM's module whitelist is undermined by the type-checker's process-global stdlib signature registry: constructing any full Vm::new() registers signatures for every stdlib module, and a later Vm::sandboxed(&amp;[...]) only restricts its ModuleRegistry but does not clear or scope those signatures. As a result, use fs; fs.exists(...) (or another withheld module) can be accepted by type checking based on stale global metadata, then fail only during module resolution/runtime rather than being rejected consistently by the sandbox capability boundary.
♻️ Previously reported (still present) (27)
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.
  • [high] Dependency form-data@4.0.5 is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.
  • [high] Dependency js-yaml@4.1.1 is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the mailto: validator scan-loop on attacker text); upgrade to at least 5.0.2.
  • [high] Dependency tmp@0.2.5 is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.
  • [medium] Dependency js-yaml@4.1.1 is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.
  • [medium] Dependency markdown-it@14.1.1 is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.
  • [medium] Dependency qs@6.15.1 is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
  • [info] Dependency memmap2@0.2.3 is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate memmap2); upgrade to at least 0.9.11.
  • [info] Dependency bare-metal@0.2.5 is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
🗑️ Suppressed and duplicate diagnostics (1)
  • Tree-sitter rejects canonical const declarations used by macros and ordinary source.
🤖 Prompt for AI agents — all findings (41)
In core/src/val/runtime_model/equality.rs around line 163, address this finding:
Structural equality does not enforce MAX_VALUE_DEPTH for object-field recursion, so deeply nested or cyclic object graphs can overflow the Rust stack instead of returning the documented comparison error.

In aot/lower/src/function.rs around line 245, address this finding:
A try body that returns can exceed the trampoline's fixed arity even though lowering accepts it, causing the generated native program to trap instead of executing the VM semantics.

In api/examples/embed.c around line 4, address this finding:
The checked-in C embedding example is now unusable because it still tells consumers to link `-llk_api`, but the `api` crate no longer emits `liblk_api.a`; only the separately named `liblk_api_cabi.a` is produced. Following the documented command on a fresh checkout therefore fails to find the archive (and does not enable the `ffi` feature).

In cli/src/native_compile.rs around line 26, address this finding:
Hybrid native compilation cannot locate the split API archive on Windows because `ensure_lk_api_staticlib` unconditionally returns `target/release/liblk_api_cabi.a`; Rust staticlib output on Windows uses the `.lib` filename (as the neighboring lkrt lookup already accounts for with `lkrt_cabi.lib`). Any Windows program that reaches the Tier 1 bridge therefore fails the link with a missing input archive even after the `lk-api-cabi` build succeeds.

In aot/driver/src/native_executable.rs around line 114, address this finding:
The Tier 1 link command leaves the known duplicate-symbol problem unsolved on macOS: both `lkrt` and `lk-api` archives contain common dependency objects, and the implementation only adds `-Wl,--allow-multiple-definition` when `!cfg!(target_os = "macos")`. On macOS the command instead passes both archives without an ld64 equivalent, so hybrid programs that require both archives can fail with duplicate definitions (or become dependent on archive extraction order), despite macOS being treated as a supported native platform by the surrounding framework/link flags.

In bare-metal-x86/program.lk around line 2811, address this finding:
Failed user-task/address-space creation leaks physical pages and permanently reduces allocator capacity.

In bare-metal-x86/program.lk around line 3869, address this finding:
The startup heap reservation consumes pages without returning them when the reservation is incomplete, so a low-memory boot loses every successfully allocated heap page even though it installs an empty heap.

In bare-metal-x86/src/boot.rs around line 24, address this finding:
The multiboot header does not request a memory map, but the kernel unconditionally initializes its page allocator from the memory-map API; under a compliant multiboot loader `entry_count()` is therefore zero, so `usable_length` is zero and all task/user/heap page allocations fail.

In ecosystem/tree-sitter-lk/grammar.js around line 788, address this finding:
Tree-sitter does not accept the canonical `try` expression form introduced by this change.

In ecosystem/tree-sitter-lk/grammar.js around line 476, address this finding:
Tree-sitter grammar rejects the canonical `defer` statement, so valid deferred code is shown as syntax errors in editor integrations.

In ecosystem/tree-sitter-lk/grammar.js around line 568, address this finding:
Tree-sitter macro groups cannot parse canonical internal macro rules using `@` (and therefore cannot parse the new recursive macro example).

In website/src/stdlib/STDLIB.md, address this finding:
The published website stdlib reference is not synchronized with the changed runtime/stdlib surface: it still documents `chan.recv` as returning `[ok, value]` and lists only `task.spawn`/`task.await`, while the implementation and the updated `docs/concurrency.md` say `recv` returns the value and raises after close, and `task` also exposes `try_await`, `join_all`, and `sleep` (including list/variadic join_all). It also omits `encoding.json/yaml/toml.stringify`, despite those exports being present in `stdlib/crates/encoding` and covered by the AOT differential tests. A user following the website can destructure a non-existent status pair or miss the serializer APIs, so the public compatibility claim is false.

In docs/aot/aot-gaps-and-lkrt.md, address this finding:
The AOT gap document still presents retired/nonexistent LLVM implementation paths as if they describe the current backend. For example it says the current entry point is `compile_native_scalar_main_artifact` under `llvm/src/llvm/backend.rs`, refers readers to `llvm/src/llvm/diagnostics.rs` and `llvm/src/llvm/dynamic_containers/`, and gives `cargo test -p lk-llvm` as validation, but this scope has moved the implementation into `aot/lower`, `aot/codegen`, and `aot/driver` (and there is no `lk-llvm` workspace package). A maintainer using the document to investigate an AOT gap will be sent to paths that cannot be opened and a package command that cannot run, so the documented AOT limitations and verification procedure do not match the actual compiler/runtime platform.

In core/src/typ/stdlib_sig.rs around line 100, address this finding:
The sandboxed VM's module whitelist is undermined by the type-checker's process-global stdlib signature registry: constructing any full `Vm::new()` registers signatures for every stdlib module, and a later `Vm::sandboxed(&[...])` only restricts its ModuleRegistry but does not clear or scope those signatures. As a result, `use fs; fs.exists(...)` (or another withheld module) can be accepted by type checking based on stale global metadata, then fail only during module resolution/runtime rather than being rejected consistently by the sandbox capability boundary.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `form-data@4.0.5` is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the `mailto:` validator scan-loop on attacker text); upgrade to at least 5.0.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `tmp@0.2.5` is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `markdown-it@14.1.1` is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `qs@6.15.1` is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.

In Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.

In Cargo.lock, address this finding:
Dependency `memmap2@0.2.3` is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate `memmap2`); upgrade to at least 0.9.11.

In bare-metal/Cargo.lock, address this finding:
Dependency `bare-metal@0.2.5` is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.

In ecosystem/zed-ext/Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.
📜 Review details

Coverage

  • scopes: 6/8 complete

let Some(right_value) = right.fields.get(name) else {
return Ok(false);
};
if !self.values(left_value, right_value, depth + 1)? {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟠 High

Structural equality does not enforce MAX_VALUE_DEPTH for object-field recursion, so deeply nested or cyclic object graphs can overflow the Rust stack instead of returning the documented comparison error.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In core/src/val/runtime_model/equality.rs, address this finding:
Structural equality does not enforce MAX_VALUE_DEPTH for object-field recursion, so deeply nested or cyclic object graphs can overflow the Rust stack instead of returning the documented comparison error.

To have the bot fix this, comment @winnowl fix.

Comment thread aot/lower/src/function.rs Outdated
}
// The trampoline passes machine words and the arity switch caps them;
// inputs and cells share that budget.
if cells.len()

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Error Handling | 🟠 High

A try body that returns can exceed the trampoline's fixed arity even though lowering accepts it, causing the generated native program to trap instead of executing the VM semantics.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ I did not execute a native compilation, but the arity arithmetic and trampoline dispatch are directly evident from the reviewed code.
🤖 Prompt for AI agents
In aot/lower/src/function.rs, address this finding:
A try body that returns can exceed the trampoline's fixed arity even though lowering accepts it, causing the generated native program to trap instead of executing the VM semantics.

To have the bot fix this, comment @winnowl fix.

Comment thread bare-metal-x86/program.lk
let pdpt = page_alloc(SHARED_PAGES);
let directory = page_alloc(SHARED_PAGES);
let table = page_alloc(SHARED_PAGES);
if (pml4 == 0 || pdpt == 0 || directory == 0 || table == 0) {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Resource Lifetime | 🟡 Medium

Failed user-task/address-space creation leaks physical pages and permanently reduces allocator capacity.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In bare-metal-x86/program.lk, address this finding:
Failed user-task/address-space creation leaks physical pages and permanently reduces allocator capacity.

To have the bot fix this, comment @winnowl fix.

// what it can guess.
".long 0x00000001", // flags: MEMORY_INFO
".long -(0x1BADB002 + 1)", // checksum
".long 0x00000001", // flags: MEMORY_INFO

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟡 Medium

The multiboot header does not request a memory map, but the kernel unconditionally initializes its page allocator from the memory-map API; under a compliant multiboot loader entry_count() is therefore zero, so usable_length is zero and all task/user/heap page allocations fail.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ A particular loader such as GRUB may populate a memory map anyway, so reproduction depends on loader-provided Multiboot information; the defect is the lack of a valid fallback when bit 6 is absent.
🤖 Prompt for AI agents
In bare-metal-x86/src/boot.rs, address this finding:
The multiboot header does not request a memory map, but the kernel unconditionally initializes its page allocator from the memory-map API; under a compliant multiboot loader `entry_count()` is therefore zero, so `usable_length` is zero and all task/user/heap page allocations fail.

To have the bot fix this, comment @winnowl fix.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🔎 Confirmed findings (3)
  • medium A top-level defer is silently accepted and rewritten as if it belonged to the whole program rather than rejected as an unsupported placement. (inline)
  • medium Map structural equality is representation-sensitive for string keys: a specialized string map and a mixed map containing the same short string key compare unequal even when their logical entries and values are identical. (inline)
  • medium build_user_space leaks every page it has already allocated when any of its four page allocations fails. The function allocates pml4, pdpt, directory, and table, immediately returns 0 if any is zero, and has no rollback; on a low-memory boot this leaves up to three allocated pages permanently counted as used. The caller then continues with a failed user task, so this is an early-return resource-lifetime violation rather than a harmless allocation failure. This would be disproved only if page_alloc were guaranteed to fail before allocating any earlier page whenever a later page is unavailable (or if the allocator reclaimed these pages elsewhere), neither of which is true for the sequential allocator. (inline)

⛔ Unresolved from previous review (13) — not approved until fixed

  • The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.
  • aot/lower/src/function.rs: return; inside an outlined try body does not propagate the enclosing-function return channel. The region scanner marks Opcode::Return0 as body_returns and the caller allocates/passes flag and value cells, but the body lowering only parks a return when matching Some(Exit::Ret(Some(reg))); Exit::Ret(None) bypasses that code and leaves the flag at its seeded false value. On the normal body-completion edge the caller therefore treats return; as a raised/catch outcome (or, for an all-return body, reaches the wrong continuation) instead of returning nil from the enclosing function. A minimal failure is try { return; } catch e { print("caught") } in a non-entry function: the VM returns immediately, while native lowering enters the catch path. This would be disproved if Return0 were converted to an Exit::Ret(Some(...)) before this match or if another lowering path sets the return flag for Ret(None), neither of which is present in the inspected code.
  • The Tier-1 hybrid bridge uses one module-global lk_hybrid_argbuf for every CallVm site, but the native runtime also supports concurrent spawned tasks. If two native threads execute a bridged call concurrently, each overwrites the shared tags/payloads before the other bridge reads them, so the VM function can receive another thread's arguments (or inconsistent tag/value pairs). The comment's single-threaded assumption is not enforced by the language/runtime.
  • cli/src/native_compile.rs: Native executable cache entries are not invalidated when the linked lkrt/runtime or toolchain changes, allowing an executable with an old ABI or runtime implementation to be reused for unchanged source.
  • aot/codegen/src/clif.rs: Tier 1 hybrid calls are not safe when native code executes concurrently: all generated CallVm sites share one writable global argument buffer, so threads can overwrite each other's tagged arguments before the bridge reads them.
  • core/src/val/runtime_model/equality.rs: Structural equality of nested objects bypasses the depth guard and can recurse until Rust stack overflow.
  • aot/codegen/src/clif.rs: The Tier-1 hybrid argument buffer is a single mutable module-global, but native spawn/go execute compiled callbacks on OS threads. Two concurrent hybrid calls (or a callback racing the parent) overwrite tags/values between marshaling and lk_hybrid_call_*, causing the VM to receive another call's arguments.
  • The native dynamic-value work adds Set and Bytes carrier tags (and documents them as values that can cross a bridged return), but the hybrid VM bridge still only marshals Nil/Bool/Int/Float/Str/List/Map: any VM function returning a Set or Bytes reaches the Some(other) => hybrid_die(...) arm and terminates instead of returning the new LkDyn. The public header likewise advertises tags only through MAP, so a Tier-1 hybrid caller cannot consume these values despite the runtime/ABI supporting them.
  • bare-metal-x86/src/user.rs: The ring-3 syscall trampoline enters the compiled dispatcher with a misaligned stack: after the CPU's ring transition, the seven pushes leave RSP 16-byte aligned, but sub rsp, 264 changes it to RSP%16 == 8 immediately before call lk_syscall_dispatch (the ABI requires the caller's RSP to be 0 mod 16 before a call). A dispatcher or callee that emits an aligned SSE stack spill can fault or misbehave on every syscall, so the user boundary is not ABI-correct; reserving 256 bytes (or otherwise explicitly aligning) would preserve the required phase. This is introduced by the new syscall trampoline; it would be disproved only if the actual native LK calling convention explicitly requires the opposite stack phase and all possible callees avoid alignment-sensitive spills.
  • core/src/stmt/defer.rs: defer inside if let/while let is neither rejected nor rewritten, so a deferred statement can reach downstream passes and returns in those bodies do not release pending resources.
  • Native execution can reuse stale cached binaries after an imported module changes.
  • core/src/val/runtime_model/equality.rs: Structural equality does not enforce MAX_VALUE_DEPTH for object-field recursion, so deeply nested or cyclic object graphs can overflow the Rust stack instead of returning the documented comparison error.
  • aot/lower/src/function.rs: A try body that returns can exceed the trampoline's fixed arity even though lowering accepts it, causing the generated native program to trap instead of executing the VM semantics.
🧹 Additional findings from this change (not shown inline) (12)
  • [medium] The generated return temporary uses the ordinary user-spellable identifier __lk_defer_return, so a user declaration with that name can collide with the rewrite and either produce a duplicate-binding error or change which value is returned. For example, a function containing let __lk_defer_return = 7; defer cleanup(); return 3; is rewritten with another let __lk_defer_return in the return block. The code's collision-avoidance claim is therefore false unless the resolver reserves this name (or generated bindings use hygiene).
  • [high] Trait default bodies are copied after defer desugaring, so a defer in a trait default method is never rewritten. expand_program_source calls desugar_defers and only afterward apply_trait_defaults; the copied Stmt::Function is then passed to constructor generation but not back through the defer walker. An impl that relies on such a default therefore reaches type checking/compilation with Stmt::Defer, violating the invariant that downstream phases never see it. This is false only if trait-default parsing rejects defer in default bodies or every later consumer independently desugars copied methods.
  • [high] Structural equality does not actually enforce the advertised MAX_VALUE_DEPTH limit for nested struct fields, so a script-controlled object chain can recurse until the Rust stack overflows instead of producing a catchable comparison error.
  • [high] The hybrid bridge's shared argument buffer is not safe for concurrent native execution: two native threads can overwrite one another's tagged slots and payloads between marshaling and lk_hybrid_call_*, so the VM receives a mixed argument vector.
  • [high] Outlined statement-try regions marshal Bool arguments with an undersized store, so the trampoline can read uninitialized upper bits instead of the VM's 0/1 value.
  • [medium] The public hybrid Dyn tag contract is no longer identical to lkrt's LkDyn tag space: the C header and API mirror define only tags 0..6, while lkrt additionally defines DYN_RAW=7, DYN_SET=8, DYN_BYTES=9, and typed-map tags 10..14. This violates the documented 'LkHybridDyn mirrors lkrt::LkDyn' contract and leaves C consumers without constants/contract for values that the runtime carrier can represent.
  • [high] lk_hybrid_register_rt accepts null callback pointers and stores them without validation; a C embedding caller can pass a null constructor, and a later container return reaches hybrid_rt(), transmutes the zero address to a function pointer, and calls it, causing undefined behavior rather than the documented bridge failure path.
  • [medium] The startup heap reservation leaks partial page allocations on failure, and even consumes the remaining pages after the first allocation has failed. The loop calls page_alloc for every page without releasing pages already obtained when a page is missing or non-contiguous; it then initializes an empty heap. For example, if only 8 pages remain, heap_base succeeds, the next seven succeed, the eighth returns 0, and the code keeps allocating through the rest of HEAP_PAGES before discarding the whole reservation. This can starve the subsequent user address-space/task setup and violates the required failure-path cleanup. The claim would be false only if the allocator were transactional or pages_init reclaimed the discarded pages, but pages_init merely overwrites allocator state and does not reclaim them.
  • [medium] The correctness CI and Makefile invoke a nonexistent Cargo integration test target, so the sanitized differential jobs (and make sanitized-differential/make asan-lkrt) fail before running any corpus.
  • [medium] General completion advertises Str as a valid type even though LK has no Str type, causing completion to suggest code that the checker rejects.
  • [medium] The host Value::Map bridge silently changes maps with non-string keys: value_from_runtime stringifies integer keys, while value_to_runtime always rebuilds them as Arc&lt;str&gt; keys. Thus eval_value/set_global are not an inverse across the core VM's supported integer-keyed maps; a host round-trip changes lookup/equality semantics (for example, key 1 becomes key "1").
  • [high] The hybrid return bridge advertises the same LkDyn carrier and DYN_LIST/DYN_MAP tags as lkrt, but its container-return path is only wired for the injected list/map constructors and cannot marshal typed string lists (it unconditionally dies for TypedList::String). Consequently a VM function returning a valid string list can be selected by the AOT hybrid lowering and then terminate the native process at the bridge, while the VM and ordinary native stdlib paths return it normally.
♻️ Previously reported (still present) (27)
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.
  • [high] Dependency form-data@4.0.5 is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.
  • [high] Dependency js-yaml@4.1.1 is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the mailto: validator scan-loop on attacker text); upgrade to at least 5.0.2.
  • [high] Dependency tmp@0.2.5 is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.
  • [medium] Dependency js-yaml@4.1.1 is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.
  • [medium] Dependency markdown-it@14.1.1 is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.
  • [medium] Dependency qs@6.15.1 is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
  • [info] Dependency memmap2@0.2.3 is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate memmap2); upgrade to at least 0.9.11.
  • [info] Dependency bare-metal@0.2.5 is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
🤖 Prompt for AI agents — all findings (42)
In core/src/stmt/defer.rs around line 104, address this finding:
A top-level `defer` is silently accepted and rewritten as if it belonged to the whole program rather than rejected as an unsupported placement.

In core/src/stmt/defer.rs around line 101, address this finding:
The generated return temporary uses the ordinary user-spellable identifier `__lk_defer_return`, so a user declaration with that name can collide with the rewrite and either produce a duplicate-binding error or change which value is returned. For example, a function containing `let __lk_defer_return = 7; defer cleanup(); return 3;` is rewritten with another `let __lk_defer_return` in the return block. The code's collision-avoidance claim is therefore false unless the resolver reserves this name (or generated bindings use hygiene).

In core/src/syntax.rs around line 101, address this finding:
Trait default bodies are copied after defer desugaring, so a `defer` in a trait default method is never rewritten. `expand_program_source` calls `desugar_defers` and only afterward `apply_trait_defaults`; the copied `Stmt::Function` is then passed to constructor generation but not back through the defer walker. An impl that relies on such a default therefore reaches type checking/compilation with `Stmt::Defer`, violating the invariant that downstream phases never see it. This is false only if trait-default parsing rejects `defer` in default bodies or every later consumer independently desugars copied methods.

In core/src/val/runtime_model/equality.rs around line 163, address this finding:
Structural equality does not actually enforce the advertised MAX_VALUE_DEPTH limit for nested struct fields, so a script-controlled object chain can recurse until the Rust stack overflows instead of producing a catchable comparison error.

In core/src/val/runtime_model/equality.rs around line 362, address this finding:
Map structural equality is representation-sensitive for string keys: a specialized string map and a mixed map containing the same short string key compare unequal even when their logical entries and values are identical.

In aot/codegen/src/clif.rs around line 356, address this finding:
The hybrid bridge's shared argument buffer is not safe for concurrent native execution: two native threads can overwrite one another's tagged slots and payloads between marshaling and `lk_hybrid_call_*`, so the VM receives a mixed argument vector.

In aot/codegen/src/clif.rs around line 896, address this finding:
Outlined statement-try regions marshal `Bool` arguments with an undersized store, so the trampoline can read uninitialized upper bits instead of the VM's 0/1 value.

In api/include/lk.h around line 80, address this finding:
The public hybrid Dyn tag contract is no longer identical to lkrt's LkDyn tag space: the C header and API mirror define only tags 0..6, while lkrt additionally defines DYN_RAW=7, DYN_SET=8, DYN_BYTES=9, and typed-map tags 10..14. This violates the documented 'LkHybridDyn mirrors lkrt::LkDyn' contract and leaves C consumers without constants/contract for values that the runtime carrier can represent.

In api/src/lib.rs around line 1101, address this finding:
lk_hybrid_register_rt accepts null callback pointers and stores them without validation; a C embedding caller can pass a null constructor, and a later container return reaches hybrid_rt(), transmutes the zero address to a function pointer, and calls it, causing undefined behavior rather than the documented bridge failure path.

In bare-metal-x86/program.lk around line 2811, address this finding:
`build_user_space` leaks every page it has already allocated when any of its four page allocations fails. The function allocates `pml4`, `pdpt`, `directory`, and `table`, immediately returns 0 if any is zero, and has no rollback; on a low-memory boot this leaves up to three allocated pages permanently counted as used. The caller then continues with a failed user task, so this is an early-return resource-lifetime violation rather than a harmless allocation failure. This would be disproved only if `page_alloc` were guaranteed to fail before allocating any earlier page whenever a later page is unavailable (or if the allocator reclaimed these pages elsewhere), neither of which is true for the sequential allocator.

In bare-metal-x86/program.lk, address this finding:
The startup heap reservation leaks partial page allocations on failure, and even consumes the remaining pages after the first allocation has failed. The loop calls `page_alloc` for every page without releasing pages already obtained when a page is missing or non-contiguous; it then initializes an empty heap. For example, if only 8 pages remain, `heap_base` succeeds, the next seven succeed, the eighth returns 0, and the code keeps allocating through the rest of `HEAP_PAGES` before discarding the whole reservation. This can starve the subsequent user address-space/task setup and violates the required failure-path cleanup. The claim would be false only if the allocator were transactional or `pages_init` reclaimed the discarded pages, but `pages_init` merely overwrites allocator state and does not reclaim them.

In .github/workflows/correctness.yml, address this finding:
The correctness CI and Makefile invoke a nonexistent Cargo integration test target, so the sanitized differential jobs (and `make sanitized-differential`/`make asan-lkrt`) fail before running any corpus.

In completion/src/lib.rs around line 1229, address this finding:
General completion advertises `Str` as a valid type even though LK has no `Str` type, causing completion to suggest code that the checker rejects.

In api/src/lib.rs around line 383, address this finding:
The host `Value::Map` bridge silently changes maps with non-string keys: `value_from_runtime` stringifies integer keys, while `value_to_runtime` always rebuilds them as `Arc<str>` keys. Thus `eval_value`/`set_global` are not an inverse across the core VM's supported integer-keyed maps; a host round-trip changes lookup/equality semantics (for example, key `1` becomes key `"1"`).

In api/src/lib.rs around line 1224, address this finding:
The hybrid return bridge advertises the same `LkDyn` carrier and `DYN_LIST`/`DYN_MAP` tags as `lkrt`, but its container-return path is only wired for the injected list/map constructors and cannot marshal typed string lists (it unconditionally dies for `TypedList::String`). Consequently a VM function returning a valid string list can be selected by the AOT hybrid lowering and then terminate the native process at the bridge, while the VM and ordinary native stdlib paths return it normally.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `form-data@4.0.5` is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the `mailto:` validator scan-loop on attacker text); upgrade to at least 5.0.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `tmp@0.2.5` is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `markdown-it@14.1.1` is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `qs@6.15.1` is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.

In Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.

In Cargo.lock, address this finding:
Dependency `memmap2@0.2.3` is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate `memmap2`); upgrade to at least 0.9.11.

In bare-metal/Cargo.lock, address this finding:
Dependency `bare-metal@0.2.5` is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.

In ecosystem/zed-ext/Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.
📜 Review details

Coverage

  • scopes: 5/7 complete

Comment thread core/src/stmt/defer.rs
const RETURN_SLOT: &str = "__lk_defer_return";

/// Rewrites a program so every `defer` runs on the way out of its function.
pub fn desugar_defers(statements: &mut Vec<Box<Stmt>>) -> Result<(), String> {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟡 Medium

A top-level defer is silently accepted and rewritten as if it belonged to the whole program rather than rejected as an unsupported placement.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In core/src/stmt/defer.rs, address this finding:
A top-level `defer` is silently accepted and rewritten as if it belonged to the whole program rather than rejected as an unsupported placement.

To have the bot fix this, comment @winnowl fix.

}

fn map_value(&self, right: &TypedMap, key: &RuntimeMapKey, left_value: &RuntimeVal, depth: u32) -> Result<bool> {
let Some(right_value) = right.get(key) else {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Data Integrity | 🟡 Medium

Map structural equality is representation-sensitive for string keys: a specialized string map and a mixed map containing the same short string key compare unequal even when their logical entries and values are identical.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In core/src/val/runtime_model/equality.rs, address this finding:
Map structural equality is representation-sensitive for string keys: a specialized string map and a mixed map containing the same short string key compare unequal even when their logical entries and values are identical.

To have the bot fix this, comment @winnowl fix.

Comment thread bare-metal-x86/program.lk
let pdpt = page_alloc(SHARED_PAGES);
let directory = page_alloc(SHARED_PAGES);
let table = page_alloc(SHARED_PAGES);
if (pml4 == 0 || pdpt == 0 || directory == 0 || table == 0) {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Resource Lifetime | 🟡 Medium

build_user_space leaks every page it has already allocated when any of its four page allocations fails. The function allocates pml4, pdpt, directory, and table, immediately returns 0 if any is zero, and has no rollback; on a low-memory boot this leaves up to three allocated pages permanently counted as used. The caller then continues with a failed user task, so this is an early-return resource-lifetime violation rather than a harmless allocation failure. This would be disproved only if page_alloc were guaranteed to fail before allocating any earlier page whenever a later page is unavailable (or if the allocator reclaimed these pages elsewhere), neither of which is true for the sequential allocator.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ The exact baseline implementation is not available, so introduction is inferred from this change adding the user-space builder and its allocator-backed address-space setup.
🤖 Prompt for AI agents
In bare-metal-x86/program.lk, address this finding:
`build_user_space` leaks every page it has already allocated when any of its four page allocations fails. The function allocates `pml4`, `pdpt`, `directory`, and `table`, immediately returns 0 if any is zero, and has no rollback; on a low-memory boot this leaves up to three allocated pages permanently counted as used. The caller then continues with a failed user task, so this is an early-return resource-lifetime violation rather than a harmless allocation failure. This would be disproved only if `page_alloc` were guaranteed to fail before allocating any earlier page whenever a later page is unavailable (or if the allocator reclaimed these pages elsewhere), neither of which is true for the sequential allocator.

To have the bot fix this, comment @winnowl fix.

@lollipopkit

ghost commented Jul 31, 2026

Copy link
Copy Markdown
Owner Author

@winnowl ignore

@winnowl

ghost commented Jul 31, 2026

Copy link
Copy Markdown

Could not tell which finding to ignore. Reply @winnowl ignore under that finding's inline comment — a top-level comment does not identify one.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

⛔ Unresolved from previous review (15) — not approved until fixed

  • The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.
  • aot/lower/src/function.rs: return; inside an outlined try body does not propagate the enclosing-function return channel. The region scanner marks Opcode::Return0 as body_returns and the caller allocates/passes flag and value cells, but the body lowering only parks a return when matching Some(Exit::Ret(Some(reg))); Exit::Ret(None) bypasses that code and leaves the flag at its seeded false value. On the normal body-completion edge the caller therefore treats return; as a raised/catch outcome (or, for an all-return body, reaches the wrong continuation) instead of returning nil from the enclosing function. A minimal failure is try { return; } catch e { print("caught") } in a non-entry function: the VM returns immediately, while native lowering enters the catch path. This would be disproved if Return0 were converted to an Exit::Ret(Some(...)) before this match or if another lowering path sets the return flag for Ret(None), neither of which is present in the inspected code.
  • The Tier-1 hybrid bridge uses one module-global lk_hybrid_argbuf for every CallVm site, but the native runtime also supports concurrent spawned tasks. If two native threads execute a bridged call concurrently, each overwrites the shared tags/payloads before the other bridge reads them, so the VM function can receive another thread's arguments (or inconsistent tag/value pairs). The comment's single-threaded assumption is not enforced by the language/runtime.
  • cli/src/native_compile.rs: Native executable cache entries are not invalidated when the linked lkrt/runtime or toolchain changes, allowing an executable with an old ABI or runtime implementation to be reused for unchanged source.
  • aot/codegen/src/clif.rs: Tier 1 hybrid calls are not safe when native code executes concurrently: all generated CallVm sites share one writable global argument buffer, so threads can overwrite each other's tagged arguments before the bridge reads them.
  • core/src/val/runtime_model/equality.rs: Structural equality of nested objects bypasses the depth guard and can recurse until Rust stack overflow.
  • aot/codegen/src/clif.rs: The Tier-1 hybrid argument buffer is a single mutable module-global, but native spawn/go execute compiled callbacks on OS threads. Two concurrent hybrid calls (or a callback racing the parent) overwrite tags/values between marshaling and lk_hybrid_call_*, causing the VM to receive another call's arguments.
  • The native dynamic-value work adds Set and Bytes carrier tags (and documents them as values that can cross a bridged return), but the hybrid VM bridge still only marshals Nil/Bool/Int/Float/Str/List/Map: any VM function returning a Set or Bytes reaches the Some(other) => hybrid_die(...) arm and terminates instead of returning the new LkDyn. The public header likewise advertises tags only through MAP, so a Tier-1 hybrid caller cannot consume these values despite the runtime/ABI supporting them.
  • bare-metal-x86/src/user.rs: The ring-3 syscall trampoline enters the compiled dispatcher with a misaligned stack: after the CPU's ring transition, the seven pushes leave RSP 16-byte aligned, but sub rsp, 264 changes it to RSP%16 == 8 immediately before call lk_syscall_dispatch (the ABI requires the caller's RSP to be 0 mod 16 before a call). A dispatcher or callee that emits an aligned SSE stack spill can fault or misbehave on every syscall, so the user boundary is not ABI-correct; reserving 256 bytes (or otherwise explicitly aligning) would preserve the required phase. This is introduced by the new syscall trampoline; it would be disproved only if the actual native LK calling convention explicitly requires the opposite stack phase and all possible callees avoid alignment-sensitive spills.
  • core/src/stmt/defer.rs: defer inside if let/while let is neither rejected nor rewritten, so a deferred statement can reach downstream passes and returns in those bodies do not release pending resources.
  • Native execution can reuse stale cached binaries after an imported module changes.
  • core/src/val/runtime_model/equality.rs: Structural equality does not enforce MAX_VALUE_DEPTH for object-field recursion, so deeply nested or cyclic object graphs can overflow the Rust stack instead of returning the documented comparison error.
  • aot/lower/src/function.rs: A try body that returns can exceed the trampoline's fixed arity even though lowering accepts it, causing the generated native program to trap instead of executing the VM semantics.
  • aot/codegen/src/clif.rs: The hybrid bridge's shared argument buffer is not safe for concurrent native execution: two native threads can overwrite one another's tagged slots and payloads between marshaling and lk_hybrid_call_*, so the VM receives a mixed argument vector.
  • api/src/lib.rs: The hybrid return bridge advertises the same LkDyn carrier and DYN_LIST/DYN_MAP tags as lkrt, but its container-return path is only wired for the injected list/map constructors and cannot marshal typed string lists (it unconditionally dies for TypedList::String). Consequently a VM function returning a valid string list can be selected by the AOT hybrid lowering and then terminate the native process at the bridge, while the VM and ordinary native stdlib paths return it normally.
🧹 Additional findings from this change (not shown inline) (16)
  • [medium] A top-level defer is silently accepted and rewritten as a fall-through release instead of being rejected as outside a function.
  • [medium] The centralized built-in signature marks String.format variadic for type-checking, but runtime arity enforcement still uses the fixed table length, so a valid multi-value format call is rejected at execution.
  • [medium] Declared optional stdlib parameters are not type-checked when supplied: both positional and named checker paths explicitly skip the parameter type whenever param.optional is true, so calls such as a string slice/replace optional Int/Bool argument with a String pass checking but runtime rejects them.
  • [medium] Trait defaults and generated struct constructors are missed when a declaration has more than one attribute wrapper: the collection/recognition helpers unwrap only one Stmt::Attributed layer, while the parser permits nested attributes, so the default method/constructor is absent downstream for doubly attributed declarations.
  • [high] The optimized BrModEqZeroIntI4/BrModNeZeroIntI4 dispatch can abort the process instead of producing the VM's defined wrapping remainder for i64::MIN % -1. These branches use Rust's % directly, and that operation panics for the signed division-overflow pair even in release builds; for example a loop/branch whose tested value is -9223372036854775808 and immediate divisor is -1 reaches this arm and panics before the catchable VM error machinery. The nearby ModInt paths explicitly use wrapping_rem, so this optimized lowering is inconsistent with the canonical arithmetic semantics and with the obligation that boundary arithmetic be recoverable/consistent. This is introduced by the new optimized branch dispatch; it would be disproven if compiler verification could prove these opcodes never receive i64::MIN (or if the language specifies aborting for this case, contrary to the adjacent wrapping implementation).
  • [high] Native/re-entrant closure calls bypass the shared call-depth limit. call_closure_value constructs a fresh Executor, moves in the shared RuntimeModuleState, and invokes run_function_inner directly, but never calls enter_lk_call/exit_lk_call; therefore a recursive path through a native boundary (for example a callable method or HOF callback that invokes a closure which re-enters the same native path) leaves RuntimeModuleState::call_depth unchanged and can recurse until Rust-stack exhaustion despite with_max_call_depth. This violates the stated cross-entry call-depth accounting and makes the resource control dependent on whether the compiler selected flattened LK dispatch versus native re-entry. It would be false only if all native/re-entrant closure call paths were proven unable to recurse or were intentionally excluded from the public call-depth limit, but the state documentation explicitly says the counter is shared to prevent this reset/bypass.
  • [high] Hybrid bridge calls are not isolated when native tasks run concurrently: every generated function shares one mutable lk_hybrid_argbuf, so two spawned threads can overwrite each other's tagged arguments between the stores and lk_hybrid_call_*. For example, two spawn0/spawn1 callbacks containing CallVm can race: thread A writes argument 1, thread B writes argument 2, then A invokes the VM with B's value (and vice versa), producing nondeterministic results despite the runtime explicitly creating OS threads. The comment's single-thread assumption is contradicted by the exported spawn implementation, and the bridge's VM mutex only serializes after arguments have already been read. This would be false only if the native lowering forbade any CallVm execution from spawned functions or otherwise serialized all bridge callers, but the runtime exposes spawn for compiled callbacks and no such synchronization is present.
  • [medium] lkrt_dyn_cast_to_i64 mishandles the newly supported typed-map dynamic tags. A typed map boxed by lkrt_dyn_from_typed_map has a tag in DYN_TMAP_BASE..DYN_TMAP_END (10..14), but the cast match only lists DYN_MAP, DYN_SET, and DYN_BYTES; a typed map therefore falls through to the wildcard and raises cannot cast Nil to an integer instead of the VM-shaped Map type error. This is observable in a caught cast error and also makes the dynamic-tag contract inconsistent with kind_name, is_map_tag, equality, display, and length, all of which recognize typed-map tags. The claim would be false only if typed-map values were proven impossible to reach dyn.cast_to_i64, despite the ABI exposing from_typed_map and using those tags for boxed values.
  • [high] Extreme negative indices can overflow instead of following the documented clamp/miss rules. read_position, element_position, and write_position compute len as i64 + *index directly; for a valid LK Int such as i64::MIN and any nonempty container, this overflows in debug builds (fatal Rust panic) and wraps in release builds, so e.g. "abc".slice(-9223372036854775808, 2) is not a catchable empty/clamped result and element/write operations can receive a bogus positive index.
  • [medium] string.to_int() and string.to_float() do not validate the required first argument before indexing values[0]. Their generated exports are variadic because of the optional/base declaration, and the bodies only reject too many arguments (or do no count check for to_float), so a direct runtime call with zero arguments panics in Rust rather than returning a catchable argument-validation error. The same malformed invocation can arise through a dynamically held native function even if normal source type checking rejects it.
  • [high] Zero-capacity channels are reported as unbuffered (ChannelValue { capacity: Some(0) }) but are created in the async runtime with Some((capacity as usize).max(1)). Consequently chan(0)/chan.new(0) has an actual one-element queue: chan.try_send(c, 1) returns true even with no receiver, whereas the documented unbuffered semantics require a send to wait/fail unless a receiver is ready. The exposed capacity() still returns 0, making the mismatch observable and allowing programs to make incorrect readiness decisions.
  • [high] The ordinary interrupt trampoline does not establish the System V x86-64 stack alignment required before calling compiled LK handlers.
  • [medium] build_user_space leaks every page already allocated when any of the four page-table allocations fails. For example, if the fourth page_alloc(SHARED_PAGES) returns 0, the first three pages remain counted as used and are never released, while the function returns 0 and the caller continues startup. Repeated/partial allocation failure therefore permanently consumes page-arena capacity and can make later task stacks or tables fail even though those pages are not in use. This is false only if page_alloc is guaranteed to succeed for all four calls whenever the function is entered, which is not guaranteed by the allocator or its callers.
  • [medium] The ASan differential jobs build a matching nightly lk-api-cabi archive but never use it, so hybrid test cases still link the normal-toolchain archive and can fail with duplicate Rust runtime symbols (or otherwise lose the intended matching-toolchain setup).
  • [medium] The aarch64 bare-metal build does not invalidate its generated LK object when the compiler binary changes. build.rs only tracks LK_BIN as an environment variable, so rebuilding the same target/debug/lk path and then running cargo build --release can reuse the old program.o and link/run stale native code; the x86 build explicitly adds cargo:rerun-if-changed for file-valued LK_BIN but the aarch64 counterpart does not.
  • [medium] AOT and VM disagree on list .slice() semantics for non-Int lists: the VM creates a live Slice window for every list carrier, but lower_method.rs lowers ListF64, ListStr, and ListDyn two-argument slices to list_h.*_slice, which materializes an independent List (and only ListI64 uses slice_h); mutations/shrinking of the source therefore produce different results between backends, and one-argument slices are also missing for those carriers.
♻️ Previously reported (still present) (27)
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.
  • [high] Dependency form-data@4.0.5 is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.
  • [high] Dependency js-yaml@4.1.1 is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the mailto: validator scan-loop on attacker text); upgrade to at least 5.0.2.
  • [high] Dependency tmp@0.2.5 is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.
  • [medium] Dependency js-yaml@4.1.1 is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.
  • [medium] Dependency markdown-it@14.1.1 is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.
  • [medium] Dependency qs@6.15.1 is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
  • [info] Dependency memmap2@0.2.3 is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate memmap2); upgrade to at least 0.9.11.
  • [info] Dependency bare-metal@0.2.5 is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
🤖 Prompt for AI agents — all findings (43)
In core/src/stmt/defer.rs around line 104, address this finding:
A top-level `defer` is silently accepted and rewritten as a fall-through release instead of being rejected as outside a function.

In core/src/vm/context/core_methods.rs around line 160, address this finding:
The centralized built-in signature marks `String.format` variadic for type-checking, but runtime arity enforcement still uses the fixed table length, so a valid multi-value format call is rejected at execution.

In core/src/typ/type_checker/expressions/stdlib.rs, address this finding:
Declared optional stdlib parameters are not type-checked when supplied: both positional and named checker paths explicitly skip the parameter type whenever `param.optional` is true, so calls such as a string slice/replace optional Int/Bool argument with a String pass checking but runtime rejects them.

In core/src/stmt/trait_defaults.rs around line 46, address this finding:
Trait defaults and generated struct constructors are missed when a declaration has more than one attribute wrapper: the collection/recognition helpers unwrap only one `Stmt::Attributed` layer, while the parser permits nested attributes, so the default method/constructor is absent downstream for doubly attributed declarations.

In core/src/vm/exec.rs, address this finding:
The optimized `BrModEqZeroIntI4`/`BrModNeZeroIntI4` dispatch can abort the process instead of producing the VM's defined wrapping remainder for `i64::MIN % -1`. These branches use Rust's `%` directly, and that operation panics for the signed division-overflow pair even in release builds; for example a loop/branch whose tested value is `-9223372036854775808` and immediate divisor is `-1` reaches this arm and panics before the catchable VM error machinery. The nearby `ModInt` paths explicitly use `wrapping_rem`, so this optimized lowering is inconsistent with the canonical arithmetic semantics and with the obligation that boundary arithmetic be recoverable/consistent. This is introduced by the new optimized branch dispatch; it would be disproven if compiler verification could prove these opcodes never receive `i64::MIN` (or if the language specifies aborting for this case, contrary to the adjacent wrapping implementation).

In core/src/vm/exec/runtime_callable.rs, address this finding:
Native/re-entrant closure calls bypass the shared call-depth limit. `call_closure_value` constructs a fresh `Executor`, moves in the shared `RuntimeModuleState`, and invokes `run_function_inner` directly, but never calls `enter_lk_call`/`exit_lk_call`; therefore a recursive path through a native boundary (for example a callable method or HOF callback that invokes a closure which re-enters the same native path) leaves `RuntimeModuleState::call_depth` unchanged and can recurse until Rust-stack exhaustion despite `with_max_call_depth`. This violates the stated cross-entry call-depth accounting and makes the resource control dependent on whether the compiler selected flattened LK dispatch versus native re-entry. It would be false only if all native/re-entrant closure call paths were proven unable to recurse or were intentionally excluded from the public call-depth limit, but the state documentation explicitly says the counter is shared to prevent this reset/bypass.

In aot/codegen/src/clif.rs around line 356, address this finding:
Hybrid bridge calls are not isolated when native tasks run concurrently: every generated function shares one mutable `lk_hybrid_argbuf`, so two spawned threads can overwrite each other's tagged arguments between the stores and `lk_hybrid_call_*`. For example, two `spawn0`/`spawn1` callbacks containing `CallVm` can race: thread A writes argument 1, thread B writes argument 2, then A invokes the VM with B's value (and vice versa), producing nondeterministic results despite the runtime explicitly creating OS threads. The comment's single-thread assumption is contradicted by the exported spawn implementation, and the bridge's VM mutex only serializes after arguments have already been read. This would be false only if the native lowering forbade any `CallVm` execution from spawned functions or otherwise serialized all bridge callers, but the runtime exposes spawn for compiled callbacks and no such synchronization is present.

In lkrt/src/lkdyn.rs around line 358, address this finding:
`lkrt_dyn_cast_to_i64` mishandles the newly supported typed-map dynamic tags. A typed map boxed by `lkrt_dyn_from_typed_map` has a tag in `DYN_TMAP_BASE..DYN_TMAP_END` (10..14), but the cast match only lists `DYN_MAP`, `DYN_SET`, and `DYN_BYTES`; a typed map therefore falls through to the wildcard and raises `cannot cast Nil to an integer` instead of the VM-shaped Map type error. This is observable in a caught cast error and also makes the dynamic-tag contract inconsistent with `kind_name`, `is_map_tag`, equality, display, and length, all of which recognize typed-map tags. The claim would be false only if typed-map values were proven impossible to reach `dyn.cast_to_i64`, despite the ABI exposing `from_typed_map` and using those tags for boxed values.

In core/src/val/position.rs around line 33, address this finding:
Extreme negative indices can overflow instead of following the documented clamp/miss rules. `read_position`, `element_position`, and `write_position` compute `len as i64 + *index` directly; for a valid LK `Int` such as `i64::MIN` and any nonempty container, this overflows in debug builds (fatal Rust panic) and wraps in release builds, so e.g. `"abc".slice(-9223372036854775808, 2)` is not a catchable empty/clamped result and element/write operations can receive a bogus positive index.

In stdlib/crates/string/src/lib.rs around line 286, address this finding:
`string.to_int()` and `string.to_float()` do not validate the required first argument before indexing `values[0]`. Their generated exports are variadic because of the optional/base declaration, and the bodies only reject too many arguments (or do no count check for `to_float`), so a direct runtime call with zero arguments panics in Rust rather than returning a catchable argument-validation error. The same malformed invocation can arise through a dynamically held native function even if normal source type checking rejects it.

In stdlib/crates/chan/src/lib.rs, address this finding:
Zero-capacity channels are reported as unbuffered (`ChannelValue { capacity: Some(0) }`) but are created in the async runtime with `Some((capacity as usize).max(1))`. Consequently `chan(0)`/`chan.new(0)` has an actual one-element queue: `chan.try_send(c, 1)` returns true even with no receiver, whereas the documented unbuffered semantics require a send to wait/fail unless a receiver is ready. The exposed `capacity()` still returns 0, making the mismatch observable and allowing programs to make incorrect readiness decisions.

In lkrt/src/isr.rs around line 107, address this finding:
The ordinary interrupt trampoline does not establish the System V x86-64 stack alignment required before calling compiled LK handlers.

In bare-metal-x86/program.lk around line 2811, address this finding:
`build_user_space` leaks every page already allocated when any of the four page-table allocations fails. For example, if the fourth `page_alloc(SHARED_PAGES)` returns 0, the first three pages remain counted as used and are never released, while the function returns 0 and the caller continues startup. Repeated/partial allocation failure therefore permanently consumes page-arena capacity and can make later task stacks or tables fail even though those pages are not in use. This is false only if `page_alloc` is guaranteed to succeed for all four calls whenever the function is entered, which is not guaranteed by the allocator or its callers.

In .github/workflows/correctness.yml around line 97, address this finding:
The ASan differential jobs build a matching nightly `lk-api-cabi` archive but never use it, so hybrid test cases still link the normal-toolchain archive and can fail with duplicate Rust runtime symbols (or otherwise lose the intended matching-toolchain setup).

In bare-metal-native/build.rs around line 22, address this finding:
The aarch64 bare-metal build does not invalidate its generated LK object when the compiler binary changes. `build.rs` only tracks `LK_BIN` as an environment variable, so rebuilding the same `target/debug/lk` path and then running `cargo build --release` can reuse the old `program.o` and link/run stale native code; the x86 build explicitly adds `cargo:rerun-if-changed` for file-valued LK_BIN but the aarch64 counterpart does not.

In aot/lower/src/lower_method.rs around line 938, address this finding:
AOT and VM disagree on list `.slice()` semantics for non-Int lists: the VM creates a live `Slice` window for every list carrier, but `lower_method.rs` lowers `ListF64`, `ListStr`, and `ListDyn` two-argument slices to `list_h.*_slice`, which materializes an independent `List` (and only `ListI64` uses `slice_h`); mutations/shrinking of the source therefore produce different results between backends, and one-argument slices are also missing for those carriers.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `form-data@4.0.5` is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the `mailto:` validator scan-loop on attacker text); upgrade to at least 5.0.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `tmp@0.2.5` is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `markdown-it@14.1.1` is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `qs@6.15.1` is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.

In Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.

In Cargo.lock, address this finding:
Dependency `memmap2@0.2.3` is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate `memmap2`); upgrade to at least 0.9.11.

In bare-metal/Cargo.lock, address this finding:
Dependency `bare-metal@0.2.5` is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.

In ecosystem/zed-ext/Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.
📜 Review details

Coverage

  • scopes: 6/8 complete

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔎 Confirmed findings (2)
  • medium A defer at program scope is silently accepted and rewritten instead of being rejected as outside a function. desugar_defers calls rewrite_sequence(statements) on the program's top-level statement vector, so a source such as defer println("x"); is removed and its body appended as ordinary top-level code; reject_stray is only used while descending blocks and is never applied to the program vector. This violates the documented placement restriction and changes an invalid form into a valid program. This is introduced by the new preprocessing entry point; it would be disproved if the parser or an earlier validation pass rejected top-level defer before desugar_defers (the shown parser accepts it generically). (inline)
  • high Structural equality does not enforce the configured depth limit for nested structs, so cyclic or sufficiently deep object graphs can recurse until a Rust stack overflow instead of returning the documented comparison error. (inline)

⛔ Unresolved from previous review (16) — not approved until fixed

  • The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.
  • aot/lower/src/function.rs: return; inside an outlined try body does not propagate the enclosing-function return channel. The region scanner marks Opcode::Return0 as body_returns and the caller allocates/passes flag and value cells, but the body lowering only parks a return when matching Some(Exit::Ret(Some(reg))); Exit::Ret(None) bypasses that code and leaves the flag at its seeded false value. On the normal body-completion edge the caller therefore treats return; as a raised/catch outcome (or, for an all-return body, reaches the wrong continuation) instead of returning nil from the enclosing function. A minimal failure is try { return; } catch e { print("caught") } in a non-entry function: the VM returns immediately, while native lowering enters the catch path. This would be disproved if Return0 were converted to an Exit::Ret(Some(...)) before this match or if another lowering path sets the return flag for Ret(None), neither of which is present in the inspected code.
  • The Tier-1 hybrid bridge uses one module-global lk_hybrid_argbuf for every CallVm site, but the native runtime also supports concurrent spawned tasks. If two native threads execute a bridged call concurrently, each overwrites the shared tags/payloads before the other bridge reads them, so the VM function can receive another thread's arguments (or inconsistent tag/value pairs). The comment's single-threaded assumption is not enforced by the language/runtime.
  • cli/src/native_compile.rs: Native executable cache entries are not invalidated when the linked lkrt/runtime or toolchain changes, allowing an executable with an old ABI or runtime implementation to be reused for unchanged source.
  • aot/codegen/src/clif.rs: Tier 1 hybrid calls are not safe when native code executes concurrently: all generated CallVm sites share one writable global argument buffer, so threads can overwrite each other's tagged arguments before the bridge reads them.
  • core/src/val/runtime_model/equality.rs: Structural equality of nested objects bypasses the depth guard and can recurse until Rust stack overflow.
  • aot/codegen/src/clif.rs: The Tier-1 hybrid argument buffer is a single mutable module-global, but native spawn/go execute compiled callbacks on OS threads. Two concurrent hybrid calls (or a callback racing the parent) overwrite tags/values between marshaling and lk_hybrid_call_*, causing the VM to receive another call's arguments.
  • The native dynamic-value work adds Set and Bytes carrier tags (and documents them as values that can cross a bridged return), but the hybrid VM bridge still only marshals Nil/Bool/Int/Float/Str/List/Map: any VM function returning a Set or Bytes reaches the Some(other) => hybrid_die(...) arm and terminates instead of returning the new LkDyn. The public header likewise advertises tags only through MAP, so a Tier-1 hybrid caller cannot consume these values despite the runtime/ABI supporting them.
  • bare-metal-x86/src/user.rs: The ring-3 syscall trampoline enters the compiled dispatcher with a misaligned stack: after the CPU's ring transition, the seven pushes leave RSP 16-byte aligned, but sub rsp, 264 changes it to RSP%16 == 8 immediately before call lk_syscall_dispatch (the ABI requires the caller's RSP to be 0 mod 16 before a call). A dispatcher or callee that emits an aligned SSE stack spill can fault or misbehave on every syscall, so the user boundary is not ABI-correct; reserving 256 bytes (or otherwise explicitly aligning) would preserve the required phase. This is introduced by the new syscall trampoline; it would be disproved only if the actual native LK calling convention explicitly requires the opposite stack phase and all possible callees avoid alignment-sensitive spills.
  • core/src/stmt/defer.rs: defer inside if let/while let is neither rejected nor rewritten, so a deferred statement can reach downstream passes and returns in those bodies do not release pending resources.
  • Native execution can reuse stale cached binaries after an imported module changes.
  • core/src/val/runtime_model/equality.rs: Structural equality does not enforce MAX_VALUE_DEPTH for object-field recursion, so deeply nested or cyclic object graphs can overflow the Rust stack instead of returning the documented comparison error.
  • aot/lower/src/function.rs: A try body that returns can exceed the trampoline's fixed arity even though lowering accepts it, causing the generated native program to trap instead of executing the VM semantics.
  • aot/codegen/src/clif.rs: The hybrid bridge's shared argument buffer is not safe for concurrent native execution: two native threads can overwrite one another's tagged slots and payloads between marshaling and lk_hybrid_call_*, so the VM receives a mixed argument vector.
  • api/src/lib.rs: The hybrid return bridge advertises the same LkDyn carrier and DYN_LIST/DYN_MAP tags as lkrt, but its container-return path is only wired for the injected list/map constructors and cannot marshal typed string lists (it unconditionally dies for TypedList::String). Consequently a VM function returning a valid string list can be selected by the AOT hybrid lowering and then terminate the native process at the bridge, while the VM and ordinary native stdlib paths return it normally.
  • aot/codegen/src/clif.rs: Hybrid bridge calls are not isolated when native tasks run concurrently: every generated function shares one mutable lk_hybrid_argbuf, so two spawned threads can overwrite each other's tagged arguments between the stores and lk_hybrid_call_*. For example, two spawn0/spawn1 callbacks containing CallVm can race: thread A writes argument 1, thread B writes argument 2, then A invokes the VM with B's value (and vice versa), producing nondeterministic results despite the runtime explicitly creating OS threads. The comment's single-thread assumption is contradicted by the exported spawn implementation, and the bridge's VM mutex only serializes after arguments have already been read. This would be false only if the native lowering forbade any CallVm execution from spawned functions or otherwise serialized all bridge callers, but the runtime exposes spawn for compiled callbacks and no such synchronization is present.
🧹 Additional findings from this change (not shown inline) (17)
  • [high] Trait default method bodies are not traversed by the defer preprocessor, so a defer inside a default method survives preprocessing and is later copied into each implementation. desugar_defers delegates to descend, whose statement match handles Impl methods but has no Trait arm, while parsed trait defaults are stored in Stmt::Trait { default_methods, .. }. For example, a trait default fn close(self) { defer release(self); return 1; } is copied by apply_trait_defaults after defer desugaring with its Stmt::Defer still present; downstream consumers then either mishandle it or bypass the required return/fall-through expansion. This is introduced by the new order/rewriter; it would be false only if trait default bodies were guaranteed to reject defer during parsing or were independently desugared before copying, neither of which is present in the examined parser/preprocessing path.
  • [medium] A failing named closure call leaks one unit of shared call depth and partially mutates the caller stack/frame state: push_call_frame_named increments depth before move_named_args_to_frame_from_stack, but propagates that error without exit_lk_call or restoring the stack. After a catchable malformed/non-string named argument error, repeated calls can hit the depth limit prematurely and the caller's named-call temporaries/frame are left altered.
  • [high] The optimized modulo-branch opcodes still use Rust's plain remainder, so i64::MIN % -1 panics instead of following the VM's wrapping remainder semantics and producing a catchable result. A program using the optimized x % -1 == 0/!= 0 loop or branch with x = i64::MIN can abort the process despite the neighboring ModInt paths being changed to wrapping_rem.
  • [high] Outlined try bodies copy jump instructions without rebasing their PC-relative targets, so any conditional/loop control flow inside a try can branch to the wrong instruction in the synthesized function.
  • [high] Vm::eval_value can overflow the host stack instead of returning an error for cyclic VM containers.
  • [medium] Sandbox fuel and heap limits do not constrain registered host functions, so a sandbox VM can execute unbounded host work and allocate outside the heap budget.
  • [high] lk_hybrid_register_rt is not atomic as a runtime-table publication: a bridge call racing registration can observe a mixed set of function pointers and invoke the wrong ABI pointer.
  • [medium] Structured map conversion is lossy and can silently collapse distinct VM keys, so eval_value/value_to_runtime do not preserve map-key primitive types or round trips.
  • [medium] fs.read_dir leaks every entry-name CString when the returned list is released before the runtime arena is torn down. lkrt_fs_read_dir_list allocates each name with owned_c_string and then registers only the Vec&lt;*const c_char&gt; via arena_handle(list), whose entry has no owned_strings collector. Consequently lkrt_rt_handle_release drops the vector but leaves all element strings in owned_strings; repeated directory reads in a long-running loop retain those allocations until global cleanup. This is introduced by the new native read-dir implementation. The claim would be false if the generated lifetime model guaranteed these lists are never released before process/thread cleanup, or if arena_handle automatically discovered and reclaimed element strings (it does not).
  • [medium] Native regex results leak strings they allocate when their container/map handle is released early. str_list allocates each split piece with arena_c_string but registers the resulting Vec&lt;*const c_char&gt; with plain arena_handle, and match_map/lkrt_regex_captures similarly allocate arena C strings embedded in a map/list without registering ownership metadata. The runtime's deep-release machinery only frees child strings for containers created with arena_handle_owning_strings; plain arena_handle drops the container but leaves those strings in the arena, so repeated regex.split, find, or captures calls in loops can grow memory until thread/process cleanup. This is introduced by the new native regex facilities. The claim would be false if those result handles were guaranteed to remain rooted until arena teardown, or if their allocated strings were intentionally shared and reclaimed elsewhere (the code allocates fresh strings and does neither).
  • [high] The native executable cache key omits imported source contents even though this change now bundles file imports during native compilation, so editing an imported .lk file can silently execute stale cached code.
  • [medium] The editor tree-sitter grammar cannot parse the newly supported try expression in an expression position such as let recovered = try { ... } catch e { ... };; try_statement is only listed as a statement and _expression has no try-expression alternative. Tree-sitter therefore produces an error tree and broken highlighting/AST mappings for valid source.
  • [medium] The editor grammars do not recognize the newly added defer syntax: the tree-sitter _statement choice has no defer statement rule, and the TextMate keyword regex omits defer. A valid defer ...; statement is consequently parsed as an error by tree-sitter and is not highlighted as a control/declaration keyword in VS Code.
  • [medium] The sanitizer differential harness does not actually use the nightly-built lk-api-cabi archive that build_lkrt_asan.sh prepares. Makefile:106-108 and .github/workflows/correctness.yml set only LKRT_STATICLIB; they omit LK_API_STATICLIB, so ensure_lk_api_staticlib() falls back to building target/release/liblk_api_cabi.a with the normal toolchain. Hybrid tests then link a nightly/ASan lkrt archive with a separately-built API archive, reintroducing the duplicate/incompatible std toolchain problem the new script explicitly claims to fix, and the intended ASan hybrid coverage can fail at link time (or use an uninstrumented/mismatched API). This is introduced by the new split static-library flow; it would be disproven if the tested suites never exercise a hybrid link or if the default and nightly archives are guaranteed ABI-compatible on all CI runners.
  • [medium] The top-level README's bare-metal CI description is stale and contradicts the newly documented/tested surface: it says 'Eleven QEMU checks run in CI', while bare-metal-x86/README.md documents seventeen check scripts and .github/workflows/check.yml invokes the x86 checks plus the Cortex-M and aarch64 QEMU smokes. A reader using README.md cannot know the actual coverage and the stated count is false after these changes. This is a documentation/maintainability regression; it would be disproven if the referenced CI workflow were intentionally not the CI being described or if the count were defined to exclude the listed checks.
  • [high] Native hybrid calls use one module-global lk_hybrid_argbuf to marshal arguments, but the runtime now exposes threaded native concurrency (spawn/tasks). Two native threads can write different argument tags/payloads into that buffer before either bridge call consumes it, so a VM callee can receive corrupted arguments or crash. The single-threaded assumption in codegen is no longer compatible with the cross-scope concurrency surface.
  • [medium] Vm::set_global calls ctx_mut(), which consumes the pending module registry and finalizes the VM context. After a host sets a global—but before any evalregister_fn and register_module still promise registration before first eval yet unconditionally expect the registry to exist and panic. Thus otherwise-independent host setup operations have an incompatible state transition: setting a global first makes later function/module registration impossible.
♻️ Previously reported (still present) (27)
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.
  • [high] Dependency form-data@4.0.5 is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.
  • [high] Dependency js-yaml@4.1.1 is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the mailto: validator scan-loop on attacker text); upgrade to at least 5.0.2.
  • [high] Dependency tmp@0.2.5 is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.
  • [medium] Dependency js-yaml@4.1.1 is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.
  • [medium] Dependency markdown-it@14.1.1 is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.
  • [medium] Dependency qs@6.15.1 is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
  • [info] Dependency memmap2@0.2.3 is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate memmap2); upgrade to at least 0.9.11.
  • [info] Dependency bare-metal@0.2.5 is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
🤖 Prompt for AI agents — all findings (46)
In core/src/stmt/defer.rs around line 104, address this finding:
A `defer` at program scope is silently accepted and rewritten instead of being rejected as outside a function. `desugar_defers` calls `rewrite_sequence(statements)` on the program's top-level statement vector, so a source such as `defer println("x");` is removed and its body appended as ordinary top-level code; `reject_stray` is only used while descending blocks and is never applied to the program vector. This violates the documented placement restriction and changes an invalid form into a valid program. This is introduced by the new preprocessing entry point; it would be disproved if the parser or an earlier validation pass rejected top-level `defer` before `desugar_defers` (the shown parser accepts it generically).

In core/src/stmt/defer.rs around line 144, address this finding:
Trait default method bodies are not traversed by the defer preprocessor, so a `defer` inside a default method survives preprocessing and is later copied into each implementation. `desugar_defers` delegates to `descend`, whose statement match handles `Impl` methods but has no `Trait` arm, while parsed trait defaults are stored in `Stmt::Trait { default_methods, .. }`. For example, a trait default `fn close(self) { defer release(self); return 1; }` is copied by `apply_trait_defaults` after defer desugaring with its `Stmt::Defer` still present; downstream consumers then either mishandle it or bypass the required return/fall-through expansion. This is introduced by the new order/rewriter; it would be false only if trait default bodies were guaranteed to reject `defer` during parsing or were independently desugared before copying, neither of which is present in the examined parser/preprocessing path.

In core/src/val/runtime_model/equality.rs around line 163, address this finding:
Structural equality does not enforce the configured depth limit for nested structs, so cyclic or sufficiently deep object graphs can recurse until a Rust stack overflow instead of returning the documented comparison error.

In core/src/vm/exec/call.rs around line 327, address this finding:
A failing named closure call leaks one unit of shared call depth and partially mutates the caller stack/frame state: `push_call_frame_named` increments depth before `move_named_args_to_frame_from_stack`, but propagates that error without `exit_lk_call` or restoring the stack. After a catchable malformed/non-string named argument error, repeated calls can hit the depth limit prematurely and the caller's named-call temporaries/frame are left altered.

In core/src/vm/exec.rs, address this finding:
The optimized modulo-branch opcodes still use Rust's plain remainder, so `i64::MIN % -1` panics instead of following the VM's wrapping remainder semantics and producing a catchable result. A program using the optimized `x % -1 == 0`/`!= 0` loop or branch with `x = i64::MIN` can abort the process despite the neighboring `ModInt` paths being changed to `wrapping_rem`.

In aot/lower/src/try_region.rs around line 139, address this finding:
Outlined try bodies copy jump instructions without rebasing their PC-relative targets, so any conditional/loop control flow inside a try can branch to the wrong instruction in the synthesized function.

In api/src/lib.rs around line 333, address this finding:
`Vm::eval_value` can overflow the host stack instead of returning an error for cyclic VM containers.

In api/src/lib.rs around line 103, address this finding:
Sandbox fuel and heap limits do not constrain registered host functions, so a sandbox VM can execute unbounded host work and allocate outside the heap budget.

In api/src/lib.rs around line 1108, address this finding:
`lk_hybrid_register_rt` is not atomic as a runtime-table publication: a bridge call racing registration can observe a mixed set of function pointers and invoke the wrong ABI pointer.

In api/src/lib.rs around line 359, address this finding:
Structured map conversion is lossy and can silently collapse distinct VM keys, so `eval_value`/`value_to_runtime` do not preserve map-key primitive types or round trips.

In lkrt/src/host.rs around line 403, address this finding:
`fs.read_dir` leaks every entry-name CString when the returned list is released before the runtime arena is torn down. `lkrt_fs_read_dir_list` allocates each name with `owned_c_string` and then registers only the `Vec<*const c_char>` via `arena_handle(list)`, whose entry has no `owned_strings` collector. Consequently `lkrt_rt_handle_release` drops the vector but leaves all element strings in `owned_strings`; repeated directory reads in a long-running loop retain those allocations until global cleanup. This is introduced by the new native read-dir implementation. The claim would be false if the generated lifetime model guaranteed these lists are never released before process/thread cleanup, or if `arena_handle` automatically discovered and reclaimed element strings (it does not).

In lkrt/src/lkregex.rs around line 73, address this finding:
Native regex results leak strings they allocate when their container/map handle is released early. `str_list` allocates each split piece with `arena_c_string` but registers the resulting `Vec<*const c_char>` with plain `arena_handle`, and `match_map`/`lkrt_regex_captures` similarly allocate arena C strings embedded in a map/list without registering ownership metadata. The runtime's deep-release machinery only frees child strings for containers created with `arena_handle_owning_strings`; plain `arena_handle` drops the container but leaves those strings in the arena, so repeated `regex.split`, `find`, or `captures` calls in loops can grow memory until thread/process cleanup. This is introduced by the new native regex facilities. The claim would be false if those result handles were guaranteed to remain rooted until arena teardown, or if their allocated strings were intentionally shared and reclaimed elsewhere (the code allocates fresh strings and does neither).

In cli/src/native_compile.rs, address this finding:
The native executable cache key omits imported source contents even though this change now bundles file imports during native compilation, so editing an imported `.lk` file can silently execute stale cached code.

In ecosystem/tree-sitter-lk/grammar.js around line 132, address this finding:
The editor tree-sitter grammar cannot parse the newly supported `try` expression in an expression position such as `let recovered = try { ... } catch e { ... };`; `try_statement` is only listed as a statement and `_expression` has no try-expression alternative. Tree-sitter therefore produces an error tree and broken highlighting/AST mappings for valid source.

In ecosystem/tree-sitter-lk/grammar.js around line 476, address this finding:
The editor grammars do not recognize the newly added `defer` syntax: the tree-sitter `_statement` choice has no defer statement rule, and the TextMate keyword regex omits `defer`. A valid `defer ...;` statement is consequently parsed as an error by tree-sitter and is not highlighted as a control/declaration keyword in VS Code.

In Makefile around line 105, address this finding:
The sanitizer differential harness does not actually use the nightly-built `lk-api-cabi` archive that `build_lkrt_asan.sh` prepares. `Makefile:106-108` and `.github/workflows/correctness.yml` set only `LKRT_STATICLIB`; they omit `LK_API_STATICLIB`, so `ensure_lk_api_staticlib()` falls back to building `target/release/liblk_api_cabi.a` with the normal toolchain. Hybrid tests then link a nightly/ASan `lkrt` archive with a separately-built API archive, reintroducing the duplicate/incompatible `std` toolchain problem the new script explicitly claims to fix, and the intended ASan hybrid coverage can fail at link time (or use an uninstrumented/mismatched API). This is introduced by the new split static-library flow; it would be disproven if the tested suites never exercise a hybrid link or if the default and nightly archives are guaranteed ABI-compatible on all CI runners.

In README.md, address this finding:
The top-level README's bare-metal CI description is stale and contradicts the newly documented/tested surface: it says 'Eleven QEMU checks run in CI', while `bare-metal-x86/README.md` documents seventeen check scripts and `.github/workflows/check.yml` invokes the x86 checks plus the Cortex-M and aarch64 QEMU smokes. A reader using README.md cannot know the actual coverage and the stated count is false after these changes. This is a documentation/maintainability regression; it would be disproven if the referenced CI workflow were intentionally not the CI being described or if the count were defined to exclude the listed checks.

In aot/codegen/src/clif.rs, address this finding:
Native hybrid calls use one module-global `lk_hybrid_argbuf` to marshal arguments, but the runtime now exposes threaded native concurrency (`spawn`/tasks). Two native threads can write different argument tags/payloads into that buffer before either bridge call consumes it, so a VM callee can receive corrupted arguments or crash. The single-threaded assumption in codegen is no longer compatible with the cross-scope concurrency surface.

In api/src/lib.rs around line 153, address this finding:
`Vm::set_global` calls `ctx_mut()`, which consumes the pending module registry and finalizes the VM context. After a host sets a global—but before any `eval`—`register_fn` and `register_module` still promise registration before first eval yet unconditionally expect the registry to exist and panic. Thus otherwise-independent host setup operations have an incompatible state transition: setting a global first makes later function/module registration impossible.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `form-data@4.0.5` is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the `mailto:` validator scan-loop on attacker text); upgrade to at least 5.0.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `tmp@0.2.5` is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `markdown-it@14.1.1` is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `qs@6.15.1` is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.

In Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.

In Cargo.lock, address this finding:
Dependency `memmap2@0.2.3` is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate `memmap2`); upgrade to at least 0.9.11.

In bare-metal/Cargo.lock, address this finding:
Dependency `bare-metal@0.2.5` is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.

In ecosystem/zed-ext/Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.
📜 Review details

Coverage

  • scopes: 7/8 complete

Comment thread core/src/stmt/defer.rs
const RETURN_SLOT: &str = "__lk_defer_return";

/// Rewrites a program so every `defer` runs on the way out of its function.
pub fn desugar_defers(statements: &mut Vec<Box<Stmt>>) -> Result<(), String> {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟡 Medium

A defer at program scope is silently accepted and rewritten instead of being rejected as outside a function. desugar_defers calls rewrite_sequence(statements) on the program's top-level statement vector, so a source such as defer println("x"); is removed and its body appended as ordinary top-level code; reject_stray is only used while descending blocks and is never applied to the program vector. This violates the documented placement restriction and changes an invalid form into a valid program. This is introduced by the new preprocessing entry point; it would be disproved if the parser or an earlier validation pass rejected top-level defer before desugar_defers (the shown parser accepts it generically).

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In core/src/stmt/defer.rs, address this finding:
A `defer` at program scope is silently accepted and rewritten instead of being rejected as outside a function. `desugar_defers` calls `rewrite_sequence(statements)` on the program's top-level statement vector, so a source such as `defer println("x");` is removed and its body appended as ordinary top-level code; `reject_stray` is only used while descending blocks and is never applied to the program vector. This violates the documented placement restriction and changes an invalid form into a valid program. This is introduced by the new preprocessing entry point; it would be disproved if the parser or an earlier validation pass rejected top-level `defer` before `desugar_defers` (the shown parser accepts it generically).

To have the bot fix this, comment @winnowl fix.

let Some(right_value) = right.fields.get(name) else {
return Ok(false);
};
if !self.values(left_value, right_value, depth + 1)? {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟠 High

Structural equality does not enforce the configured depth limit for nested structs, so cyclic or sufficiently deep object graphs can recurse until a Rust stack overflow instead of returning the documented comparison error.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ The exact Rust stack depth at which a particular deployment aborts depends on platform stack size, but the missing guard and unbounded recursive path are directly established.
🤖 Prompt for AI agents
In core/src/val/runtime_model/equality.rs, address this finding:
Structural equality does not enforce the configured depth limit for nested structs, so cyclic or sufficiently deep object graphs can recurse until a Rust stack overflow instead of returning the documented comparison error.

To have the bot fix this, comment @winnowl fix.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🔎 Confirmed findings (4)
  • medium The stdlib assertion/equality path still bypasses the canonical structural equality implementation, so equal structs (and other newly-supported aggregate values) are reported unequal. language::assert_eq calls stdlib/common::runtime_native::runtime_values_equal, whose heap_values_equal only handles String/List/Map/Set and returns false for HeapValue::Object and HeapValue::Slice (and Bytes). For example, two separately-created Point{x: 1} values compare unequal when passed to assert_eq, despite the new runtime model defining structs structurally. This is introduced/exposed by the migration leaving the old duplicate implementation active; it would be disproven if all public assertion paths were shown to call lk_core::val::runtime_values_equal or if the language contract intentionally excludes structs/slices from equality. (inline)
  • high Canonical structural equality does not enforce its depth limit for object fields. In Comparison::objects, each field is compared with self.values(left_value, right_value, depth + 1) instead of self.nested(...); therefore a chain of deeply nested structs (or a cyclic object graph if one is constructed through runtime mutation) bypasses the MAX_VALUE_DEPTH check and recurses through Rust stack frames until stack overflow, violating the stated no-process-abort comparison limit. The claim is false only if object graphs are proven impossible to nest beyond the limit/cycle in every supported runtime path, or if object equality is intentionally exempt from the depth contract. (inline)
  • medium Every time.timeout/time.after permanently retains a completed JoinHandle in the global task registry, so timer use leaks a task slot and thread handle even when the returned channel is never awaited (which is the normal timer usage). (inline)
  • medium A failed user-address-space construction leaks every page allocated before the first missing page. build_user_space allocates pml4, pdpt, directory, and table sequentially, but on any partial failure it immediately returns 0 without releasing the nonzero pages. On a nearly exhausted page arena, repeatedly attempting to spawn a user task permanently consumes up to three pages per attempt, so later allocations fail despite those pages no longer being live; the boot path also has no cleanup for this failure. This is introduced by the new dynamically allocated per-task address spaces. It would be disproven if page_alloc were guaranteed to atomically reserve all four pages or if the caller demonstrably reclaimed the partial allocations, neither of which is true here. (inline)

⛔ Unresolved from previous review (19) — not approved until fixed

  • The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.
  • aot/lower/src/function.rs: return; inside an outlined try body does not propagate the enclosing-function return channel. The region scanner marks Opcode::Return0 as body_returns and the caller allocates/passes flag and value cells, but the body lowering only parks a return when matching Some(Exit::Ret(Some(reg))); Exit::Ret(None) bypasses that code and leaves the flag at its seeded false value. On the normal body-completion edge the caller therefore treats return; as a raised/catch outcome (or, for an all-return body, reaches the wrong continuation) instead of returning nil from the enclosing function. A minimal failure is try { return; } catch e { print("caught") } in a non-entry function: the VM returns immediately, while native lowering enters the catch path. This would be disproved if Return0 were converted to an Exit::Ret(Some(...)) before this match or if another lowering path sets the return flag for Ret(None), neither of which is present in the inspected code.
  • The Tier-1 hybrid bridge uses one module-global lk_hybrid_argbuf for every CallVm site, but the native runtime also supports concurrent spawned tasks. If two native threads execute a bridged call concurrently, each overwrites the shared tags/payloads before the other bridge reads them, so the VM function can receive another thread's arguments (or inconsistent tag/value pairs). The comment's single-threaded assumption is not enforced by the language/runtime.
  • cli/src/native_compile.rs: Native executable cache entries are not invalidated when the linked lkrt/runtime or toolchain changes, allowing an executable with an old ABI or runtime implementation to be reused for unchanged source.
  • aot/codegen/src/clif.rs: Tier 1 hybrid calls are not safe when native code executes concurrently: all generated CallVm sites share one writable global argument buffer, so threads can overwrite each other's tagged arguments before the bridge reads them.
  • core/src/val/runtime_model/equality.rs: Structural equality of nested objects bypasses the depth guard and can recurse until Rust stack overflow.
  • aot/codegen/src/clif.rs: The Tier-1 hybrid argument buffer is a single mutable module-global, but native spawn/go execute compiled callbacks on OS threads. Two concurrent hybrid calls (or a callback racing the parent) overwrite tags/values between marshaling and lk_hybrid_call_*, causing the VM to receive another call's arguments.
  • The native dynamic-value work adds Set and Bytes carrier tags (and documents them as values that can cross a bridged return), but the hybrid VM bridge still only marshals Nil/Bool/Int/Float/Str/List/Map: any VM function returning a Set or Bytes reaches the Some(other) => hybrid_die(...) arm and terminates instead of returning the new LkDyn. The public header likewise advertises tags only through MAP, so a Tier-1 hybrid caller cannot consume these values despite the runtime/ABI supporting them.
  • bare-metal-x86/src/user.rs: The ring-3 syscall trampoline enters the compiled dispatcher with a misaligned stack: after the CPU's ring transition, the seven pushes leave RSP 16-byte aligned, but sub rsp, 264 changes it to RSP%16 == 8 immediately before call lk_syscall_dispatch (the ABI requires the caller's RSP to be 0 mod 16 before a call). A dispatcher or callee that emits an aligned SSE stack spill can fault or misbehave on every syscall, so the user boundary is not ABI-correct; reserving 256 bytes (or otherwise explicitly aligning) would preserve the required phase. This is introduced by the new syscall trampoline; it would be disproved only if the actual native LK calling convention explicitly requires the opposite stack phase and all possible callees avoid alignment-sensitive spills.
  • core/src/stmt/defer.rs: defer inside if let/while let is neither rejected nor rewritten, so a deferred statement can reach downstream passes and returns in those bodies do not release pending resources.
  • Native execution can reuse stale cached binaries after an imported module changes.
  • core/src/val/runtime_model/equality.rs: Structural equality does not enforce MAX_VALUE_DEPTH for object-field recursion, so deeply nested or cyclic object graphs can overflow the Rust stack instead of returning the documented comparison error.
  • aot/lower/src/function.rs: A try body that returns can exceed the trampoline's fixed arity even though lowering accepts it, causing the generated native program to trap instead of executing the VM semantics.
  • aot/codegen/src/clif.rs: The hybrid bridge's shared argument buffer is not safe for concurrent native execution: two native threads can overwrite one another's tagged slots and payloads between marshaling and lk_hybrid_call_*, so the VM receives a mixed argument vector.
  • api/src/lib.rs: The hybrid return bridge advertises the same LkDyn carrier and DYN_LIST/DYN_MAP tags as lkrt, but its container-return path is only wired for the injected list/map constructors and cannot marshal typed string lists (it unconditionally dies for TypedList::String). Consequently a VM function returning a valid string list can be selected by the AOT hybrid lowering and then terminate the native process at the bridge, while the VM and ordinary native stdlib paths return it normally.
  • aot/codegen/src/clif.rs: Hybrid bridge calls are not isolated when native tasks run concurrently: every generated function shares one mutable lk_hybrid_argbuf, so two spawned threads can overwrite each other's tagged arguments between the stores and lk_hybrid_call_*. For example, two spawn0/spawn1 callbacks containing CallVm can race: thread A writes argument 1, thread B writes argument 2, then A invokes the VM with B's value (and vice versa), producing nondeterministic results despite the runtime explicitly creating OS threads. The comment's single-thread assumption is contradicted by the exported spawn implementation, and the bridge's VM mutex only serializes after arguments have already been read. This would be false only if the native lowering forbade any CallVm execution from spawned functions or otherwise serialized all bridge callers, but the runtime exposes spawn for compiled callbacks and no such synchronization is present.
  • core/src/val/runtime_model/equality.rs: Structural equality does not enforce the configured depth limit for nested structs, so cyclic or sufficiently deep object graphs can recurse until a Rust stack overflow instead of returning the documented comparison error.
  • api/src/lib.rs: Vm::eval_value can overflow the host stack instead of returning an error for cyclic VM containers.
  • The native executable cache key omits imported source contents even though this change now bundles file imports during native compilation, so editing an imported .lk file can silently execute stale cached code.
🧹 Additional findings from this change (not shown inline) (10)
  • [high] Defer return-value parking can capture the wrong variable when user code binds __lk_defer_return. The rewrite hard-codes that identifier as a generated let, so a release cloned into the same return block resolves references to the generated return value instead of the user's outer binding.
  • [high] Defer statements in trait default method bodies are not desugared, violating the invariant that no downstream phase sees Stmt::Defer and causing defaults copied into impls to retain raw defers.
  • [high] An uncaught runtime raise in a spawned task terminates the whole native process instead of becoming a failed task that task.await can catch/report.
  • [medium] The boot-time kernel heap reservation leaks pages when its contiguous allocation is incomplete. The loop allocates the first page and then continues allocating the remaining 15 pages even after exhaustion or a non-contiguous result; when heap_pages_ok is false, it initializes an empty heap but never releases any pages already acquired. On a machine whose usable range is smaller than the requested 16 pages (or is interrupted by an unavailable/non-contiguous range), boot consumes those pages permanently and reports no heap; this can also starve the page allocator needed for user address spaces and task stacks. The claim would be false only if the page allocator could never fail or return a non-contiguous page for this reservation, but its API explicitly returns 0 at exhaustion and the caller's own check handles failure.
  • [high] The native-run cache can execute stale code after a regular imported file changes.
  • [high] lk bundle does not produce a self-contained executable for programs with file or package imports, despite the command and output path documenting a self-contained bundle.
  • [high] The scheduled correctness workflow invokes a nonexistent integration test target.
  • [medium] The bare-metal documentation still advertises a deleted slice stdlib feature and reports an invalid module count.
  • [high] The native executable cache key omits imported source files even though the AOT pipeline now bundles file imports before code generation. Editing an imported module leaves the entry source hash unchanged, so try_execute_cached_native can run a stale executable containing the old imported code instead of rebuilding.
  • [medium] The bare-metal build's rerun inputs do not cover all source files that the CLI's new import bundling can consume. build.rs watches only the drivers directory, while program.lk and bundled imports can include files outside that directory; changes to such an imported .lk file can leave program.o and the final image stale.
♻️ Previously reported (still present) (27)
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.
  • [high] Dependency form-data@4.0.5 is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.
  • [high] Dependency js-yaml@4.1.1 is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the mailto: validator scan-loop on attacker text); upgrade to at least 5.0.2.
  • [high] Dependency tmp@0.2.5 is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.
  • [medium] Dependency js-yaml@4.1.1 is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.
  • [medium] Dependency markdown-it@14.1.1 is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.
  • [medium] Dependency qs@6.15.1 is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
  • [info] Dependency memmap2@0.2.3 is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate memmap2); upgrade to at least 0.9.11.
  • [info] Dependency bare-metal@0.2.5 is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
🗑️ Suppressed and duplicate diagnostics (1)
  • The AOT design/status documentation names and instructs users to test a removed lk-llvm crate, so its documented validation commands are not runnable and the backend migration is misrepresented.
🤖 Prompt for AI agents — all findings (41)
In core/src/stmt/defer.rs around line 101, address this finding:
Defer return-value parking can capture the wrong variable when user code binds `__lk_defer_return`. The rewrite hard-codes that identifier as a generated `let`, so a release cloned into the same return block resolves references to the generated return value instead of the user's outer binding.

In core/src/stmt/defer.rs around line 144, address this finding:
Defer statements in trait default method bodies are not desugared, violating the invariant that no downstream phase sees `Stmt::Defer` and causing defaults copied into impls to retain raw defers.

In stdlib/common/src/runtime_native.rs around line 141, address this finding:
The stdlib assertion/equality path still bypasses the canonical structural equality implementation, so equal structs (and other newly-supported aggregate values) are reported unequal. `language::assert_eq` calls `stdlib/common::runtime_native::runtime_values_equal`, whose `heap_values_equal` only handles String/List/Map/Set and returns false for `HeapValue::Object` and `HeapValue::Slice` (and Bytes). For example, two separately-created `Point{x: 1}` values compare unequal when passed to `assert_eq`, despite the new runtime model defining structs structurally. This is introduced/exposed by the migration leaving the old duplicate implementation active; it would be disproven if all public assertion paths were shown to call `lk_core::val::runtime_values_equal` or if the language contract intentionally excludes structs/slices from equality.

In core/src/val/runtime_model/equality.rs around line 163, address this finding:
Canonical structural equality does not enforce its depth limit for object fields. In `Comparison::objects`, each field is compared with `self.values(left_value, right_value, depth + 1)` instead of `self.nested(...)`; therefore a chain of deeply nested structs (or a cyclic object graph if one is constructed through runtime mutation) bypasses the `MAX_VALUE_DEPTH` check and recurses through Rust stack frames until stack overflow, violating the stated no-process-abort comparison limit. The claim is false only if object graphs are proven impossible to nest beyond the limit/cycle in every supported runtime path, or if object equality is intentionally exempt from the depth contract.

In lkrt/src/chan.rs around line 628, address this finding:
An uncaught runtime raise in a spawned task terminates the whole native process instead of becoming a failed task that `task.await` can catch/report.

In lkrt/src/chan.rs around line 291, address this finding:
Every `time.timeout`/`time.after` permanently retains a completed `JoinHandle` in the global task registry, so timer use leaks a task slot and thread handle even when the returned channel is never awaited (which is the normal timer usage).

In bare-metal-x86/program.lk around line 2811, address this finding:
A failed user-address-space construction leaks every page allocated before the first missing page. `build_user_space` allocates pml4, pdpt, directory, and table sequentially, but on any partial failure it immediately returns 0 without releasing the nonzero pages. On a nearly exhausted page arena, repeatedly attempting to spawn a user task permanently consumes up to three pages per attempt, so later allocations fail despite those pages no longer being live; the boot path also has no cleanup for this failure. This is introduced by the new dynamically allocated per-task address spaces. It would be disproven if `page_alloc` were guaranteed to atomically reserve all four pages or if the caller demonstrably reclaimed the partial allocations, neither of which is true here.

In bare-metal-x86/program.lk around line 3876, address this finding:
The boot-time kernel heap reservation leaks pages when its contiguous allocation is incomplete. The loop allocates the first page and then continues allocating the remaining 15 pages even after exhaustion or a non-contiguous result; when `heap_pages_ok` is false, it initializes an empty heap but never releases any pages already acquired. On a machine whose usable range is smaller than the requested 16 pages (or is interrupted by an unavailable/non-contiguous range), boot consumes those pages permanently and reports `no heap`; this can also starve the page allocator needed for user address spaces and task stacks. The claim would be false only if the page allocator could never fail or return a non-contiguous page for this reservation, but its API explicitly returns 0 at exhaustion and the caller's own check handles failure.

In cli/src/native_compile.rs, address this finding:
The native-run cache can execute stale code after a regular imported file changes.

In cli/src/main.rs, address this finding:
`lk bundle` does not produce a self-contained executable for programs with file or package imports, despite the command and output path documenting a self-contained bundle.

In .github/workflows/correctness.yml, address this finding:
The scheduled correctness workflow invokes a nonexistent integration test target.

In bare-metal/README.md around line 120, address this finding:
The bare-metal documentation still advertises a deleted `slice` stdlib feature and reports an invalid module count.

In cli/src/native_compile.rs, address this finding:
The native executable cache key omits imported source files even though the AOT pipeline now bundles file imports before code generation. Editing an imported module leaves the entry source hash unchanged, so `try_execute_cached_native` can run a stale executable containing the old imported code instead of rebuilding.

In bare-metal-x86/build.rs around line 18, address this finding:
The bare-metal build's rerun inputs do not cover all source files that the CLI's new import bundling can consume. `build.rs` watches only the `drivers` directory, while `program.lk` and bundled imports can include files outside that directory; changes to such an imported `.lk` file can leave `program.o` and the final image stale.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `form-data@4.0.5` is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the `mailto:` validator scan-loop on attacker text); upgrade to at least 5.0.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `tmp@0.2.5` is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `markdown-it@14.1.1` is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `qs@6.15.1` is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.

In Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.

In Cargo.lock, address this finding:
Dependency `memmap2@0.2.3` is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate `memmap2`); upgrade to at least 0.9.11.

In bare-metal/Cargo.lock, address this finding:
Dependency `bare-metal@0.2.5` is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.

In ecosystem/zed-ext/Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.
📜 Review details

Coverage

  • scopes: 6/7 complete

/// held. `ShortStr` is not the bug: it is a small-string optimisation that made
/// half the cases accidentally right. Take it away and the derive is uniformly
/// wrong instead of intermittently.
pub fn runtime_values_equal(left: &RuntimeVal, right: &RuntimeVal, heap: &HeapStore) -> Result<bool> {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟡 Medium

The stdlib assertion/equality path still bypasses the canonical structural equality implementation, so equal structs (and other newly-supported aggregate values) are reported unequal. language::assert_eq calls stdlib/common::runtime_native::runtime_values_equal, whose heap_values_equal only handles String/List/Map/Set and returns false for HeapValue::Object and HeapValue::Slice (and Bytes). For example, two separately-created Point{x: 1} values compare unequal when passed to assert_eq, despite the new runtime model defining structs structurally. This is introduced/exposed by the migration leaving the old duplicate implementation active; it would be disproven if all public assertion paths were shown to call lk_core::val::runtime_values_equal or if the language contract intentionally excludes structs/slices from equality.

🧩 Analysis
  • Change relation: unknown
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In stdlib/common/src/runtime_native.rs, address this finding:
The stdlib assertion/equality path still bypasses the canonical structural equality implementation, so equal structs (and other newly-supported aggregate values) are reported unequal. `language::assert_eq` calls `stdlib/common::runtime_native::runtime_values_equal`, whose `heap_values_equal` only handles String/List/Map/Set and returns false for `HeapValue::Object` and `HeapValue::Slice` (and Bytes). For example, two separately-created `Point{x: 1}` values compare unequal when passed to `assert_eq`, despite the new runtime model defining structs structurally. This is introduced/exposed by the migration leaving the old duplicate implementation active; it would be disproven if all public assertion paths were shown to call `lk_core::val::runtime_values_equal` or if the language contract intentionally excludes structs/slices from equality.

To have the bot fix this, comment @winnowl fix.

let Some(right_value) = right.fields.get(name) else {
return Ok(false);
};
if !self.values(left_value, right_value, depth + 1)? {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟠 High

Canonical structural equality does not enforce its depth limit for object fields. In Comparison::objects, each field is compared with self.values(left_value, right_value, depth + 1) instead of self.nested(...); therefore a chain of deeply nested structs (or a cyclic object graph if one is constructed through runtime mutation) bypasses the MAX_VALUE_DEPTH check and recurses through Rust stack frames until stack overflow, violating the stated no-process-abort comparison limit. The claim is false only if object graphs are proven impossible to nest beyond the limit/cycle in every supported runtime path, or if object equality is intentionally exempt from the depth contract.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In core/src/val/runtime_model/equality.rs, address this finding:
Canonical structural equality does not enforce its depth limit for object fields. In `Comparison::objects`, each field is compared with `self.values(left_value, right_value, depth + 1)` instead of `self.nested(...)`; therefore a chain of deeply nested structs (or a cyclic object graph if one is constructed through runtime mutation) bypasses the `MAX_VALUE_DEPTH` check and recurses through Rust stack frames until stack overflow, violating the stated no-process-abort comparison limit. The claim is false only if object graphs are proven impossible to nest beyond the limit/cycle in every supported runtime path, or if object equality is intentionally exempt from the depth contract.

To have the bot fix this, comment @winnowl fix.

Comment thread lkrt/src/chan.rs
fn spawn_timer(duration_ms: i64, after: bool) -> i64 {
let id = lkrt_chan_new(1);
let delay = core::time::Duration::from_millis(duration_ms.max(0) as u64);
register_task(std::thread::spawn(move || {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Resource Lifetime | 🟡 Medium

Every time.timeout/time.after permanently retains a completed JoinHandle in the global task registry, so timer use leaks a task slot and thread handle even when the returned channel is never awaited (which is the normal timer usage).

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ The native lkrt timer path is specifically the affected deployment path; the ordinary VM stdlib timer path uses the async runtime and has separate task bookkeeping.
  • ⚠️ The completed OS thread's kernel resources may already be reclaimed on thread exit, but the JoinHandle/task slot and its registry entry are demonstrably retained, causing unbounded process-memory/registry growth.
🤖 Prompt for AI agents
In lkrt/src/chan.rs, address this finding:
Every `time.timeout`/`time.after` permanently retains a completed `JoinHandle` in the global task registry, so timer use leaks a task slot and thread handle even when the returned channel is never awaited (which is the normal timer usage).

To have the bot fix this, comment @winnowl fix.

Comment thread bare-metal-x86/program.lk
let pdpt = page_alloc(SHARED_PAGES);
let directory = page_alloc(SHARED_PAGES);
let table = page_alloc(SHARED_PAGES);
if (pml4 == 0 || pdpt == 0 || directory == 0 || table == 0) {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Resource Lifetime | 🟡 Medium

A failed user-address-space construction leaks every page allocated before the first missing page. build_user_space allocates pml4, pdpt, directory, and table sequentially, but on any partial failure it immediately returns 0 without releasing the nonzero pages. On a nearly exhausted page arena, repeatedly attempting to spawn a user task permanently consumes up to three pages per attempt, so later allocations fail despite those pages no longer being live; the boot path also has no cleanup for this failure. This is introduced by the new dynamically allocated per-task address spaces. It would be disproven if page_alloc were guaranteed to atomically reserve all four pages or if the caller demonstrably reclaimed the partial allocations, neither of which is true here.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ The current program invokes build_user_space only twice during boot rather than exposing a general repeated spawn path, so the repeated-attempt scenario is narrower than stated; the partial-allocation leak itself is directly reachable on either boot call.
🤖 Prompt for AI agents
In bare-metal-x86/program.lk, address this finding:
A failed user-address-space construction leaks every page allocated before the first missing page. `build_user_space` allocates pml4, pdpt, directory, and table sequentially, but on any partial failure it immediately returns 0 without releasing the nonzero pages. On a nearly exhausted page arena, repeatedly attempting to spawn a user task permanently consumes up to three pages per attempt, so later allocations fail despite those pages no longer being live; the boot path also has no cleanup for this failure. This is introduced by the new dynamically allocated per-task address spaces. It would be disproven if `page_alloc` were guaranteed to atomically reserve all four pages or if the caller demonstrably reclaimed the partial allocations, neither of which is true here.

To have the bot fix this, comment @winnowl fix.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🔎 Confirmed findings (3)
  • high Trait default methods containing defer are never desugared, so a defer survives into type checking/compilation after the promised pre-consumer rewrite. desugar_defers descends into functions and impl methods but has no Stmt::Trait arm; expand_program_source then copies trait defaults into impls after desugaring. A program such as trait T { fn f() { defer release(); return 1; } } impl T for S {} therefore copies a raw Stmt::Defer into the generated method (and even a trait default used nowhere remains unprocessed), violating the invariant that downstream consumers never see Stmt::Defer. This is introduced by combining trait-default expansion with the existing defer traversal; it would be false only if trait default bodies are guaranteed by the parser/macros never to contain defer or are rewritten elsewhere before checking. (inline)
  • high if let is omitted from both defer traversal and release insertion. With a top-level defer followed by if let p = value { return ... }, with_releases matches no Stmt::IfLet arm and leaves the nested return unreleased; with a defer written inside the if let body, descend also has no IfLet arm, so reject_stray never reports the forbidden placement and the raw defer reaches downstream consumers. This is a concrete missing branch variant in the AST rewrite and would be false only if IfLet were impossible in function bodies, which the parser and compiler explicitly support. (inline)
  • high Structural equality of objects bypasses the depth guard and can recurse until Rust stack overflow on deeply nested/cyclic structs. (inline)

⛔ Unresolved from previous review (23) — not approved until fixed

  • The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.
  • aot/lower/src/function.rs: return; inside an outlined try body does not propagate the enclosing-function return channel. The region scanner marks Opcode::Return0 as body_returns and the caller allocates/passes flag and value cells, but the body lowering only parks a return when matching Some(Exit::Ret(Some(reg))); Exit::Ret(None) bypasses that code and leaves the flag at its seeded false value. On the normal body-completion edge the caller therefore treats return; as a raised/catch outcome (or, for an all-return body, reaches the wrong continuation) instead of returning nil from the enclosing function. A minimal failure is try { return; } catch e { print("caught") } in a non-entry function: the VM returns immediately, while native lowering enters the catch path. This would be disproved if Return0 were converted to an Exit::Ret(Some(...)) before this match or if another lowering path sets the return flag for Ret(None), neither of which is present in the inspected code.
  • The Tier-1 hybrid bridge uses one module-global lk_hybrid_argbuf for every CallVm site, but the native runtime also supports concurrent spawned tasks. If two native threads execute a bridged call concurrently, each overwrites the shared tags/payloads before the other bridge reads them, so the VM function can receive another thread's arguments (or inconsistent tag/value pairs). The comment's single-threaded assumption is not enforced by the language/runtime.
  • cli/src/native_compile.rs: Native executable cache entries are not invalidated when the linked lkrt/runtime or toolchain changes, allowing an executable with an old ABI or runtime implementation to be reused for unchanged source.
  • aot/codegen/src/clif.rs: Tier 1 hybrid calls are not safe when native code executes concurrently: all generated CallVm sites share one writable global argument buffer, so threads can overwrite each other's tagged arguments before the bridge reads them.
  • core/src/val/runtime_model/equality.rs: Structural equality of nested objects bypasses the depth guard and can recurse until Rust stack overflow.
  • aot/codegen/src/clif.rs: The Tier-1 hybrid argument buffer is a single mutable module-global, but native spawn/go execute compiled callbacks on OS threads. Two concurrent hybrid calls (or a callback racing the parent) overwrite tags/values between marshaling and lk_hybrid_call_*, causing the VM to receive another call's arguments.
  • The native dynamic-value work adds Set and Bytes carrier tags (and documents them as values that can cross a bridged return), but the hybrid VM bridge still only marshals Nil/Bool/Int/Float/Str/List/Map: any VM function returning a Set or Bytes reaches the Some(other) => hybrid_die(...) arm and terminates instead of returning the new LkDyn. The public header likewise advertises tags only through MAP, so a Tier-1 hybrid caller cannot consume these values despite the runtime/ABI supporting them.
  • bare-metal-x86/src/user.rs: The ring-3 syscall trampoline enters the compiled dispatcher with a misaligned stack: after the CPU's ring transition, the seven pushes leave RSP 16-byte aligned, but sub rsp, 264 changes it to RSP%16 == 8 immediately before call lk_syscall_dispatch (the ABI requires the caller's RSP to be 0 mod 16 before a call). A dispatcher or callee that emits an aligned SSE stack spill can fault or misbehave on every syscall, so the user boundary is not ABI-correct; reserving 256 bytes (or otherwise explicitly aligning) would preserve the required phase. This is introduced by the new syscall trampoline; it would be disproved only if the actual native LK calling convention explicitly requires the opposite stack phase and all possible callees avoid alignment-sensitive spills.
  • core/src/stmt/defer.rs: defer inside if let/while let is neither rejected nor rewritten, so a deferred statement can reach downstream passes and returns in those bodies do not release pending resources.
  • Native execution can reuse stale cached binaries after an imported module changes.
  • core/src/val/runtime_model/equality.rs: Structural equality does not enforce MAX_VALUE_DEPTH for object-field recursion, so deeply nested or cyclic object graphs can overflow the Rust stack instead of returning the documented comparison error.
  • aot/lower/src/function.rs: A try body that returns can exceed the trampoline's fixed arity even though lowering accepts it, causing the generated native program to trap instead of executing the VM semantics.
  • aot/codegen/src/clif.rs: The hybrid bridge's shared argument buffer is not safe for concurrent native execution: two native threads can overwrite one another's tagged slots and payloads between marshaling and lk_hybrid_call_*, so the VM receives a mixed argument vector.
  • api/src/lib.rs: The hybrid return bridge advertises the same LkDyn carrier and DYN_LIST/DYN_MAP tags as lkrt, but its container-return path is only wired for the injected list/map constructors and cannot marshal typed string lists (it unconditionally dies for TypedList::String). Consequently a VM function returning a valid string list can be selected by the AOT hybrid lowering and then terminate the native process at the bridge, while the VM and ordinary native stdlib paths return it normally.
  • aot/codegen/src/clif.rs: Hybrid bridge calls are not isolated when native tasks run concurrently: every generated function shares one mutable lk_hybrid_argbuf, so two spawned threads can overwrite each other's tagged arguments between the stores and lk_hybrid_call_*. For example, two spawn0/spawn1 callbacks containing CallVm can race: thread A writes argument 1, thread B writes argument 2, then A invokes the VM with B's value (and vice versa), producing nondeterministic results despite the runtime explicitly creating OS threads. The comment's single-thread assumption is contradicted by the exported spawn implementation, and the bridge's VM mutex only serializes after arguments have already been read. This would be false only if the native lowering forbade any CallVm execution from spawned functions or otherwise serialized all bridge callers, but the runtime exposes spawn for compiled callbacks and no such synchronization is present.
  • core/src/val/runtime_model/equality.rs: Structural equality does not enforce the configured depth limit for nested structs, so cyclic or sufficiently deep object graphs can recurse until a Rust stack overflow instead of returning the documented comparison error.
  • api/src/lib.rs: Vm::eval_value can overflow the host stack instead of returning an error for cyclic VM containers.
  • The native executable cache key omits imported source contents even though this change now bundles file imports during native compilation, so editing an imported .lk file can silently execute stale cached code.
  • core/src/val/runtime_model/equality.rs: Canonical structural equality does not enforce its depth limit for object fields. In Comparison::objects, each field is compared with self.values(left_value, right_value, depth + 1) instead of self.nested(...); therefore a chain of deeply nested structs (or a cyclic object graph if one is constructed through runtime mutation) bypasses the MAX_VALUE_DEPTH check and recurses through Rust stack frames until stack overflow, violating the stated no-process-abort comparison limit. The claim is false only if object graphs are proven impossible to nest beyond the limit/cycle in every supported runtime path, or if object equality is intentionally exempt from the depth contract.
  • lkrt/src/chan.rs: An uncaught runtime raise in a spawned task terminates the whole native process instead of becoming a failed task that task.await can catch/report.
  • lk bundle does not produce a self-contained executable for programs with file or package imports, despite the command and output path documenting a self-contained bundle.
  • The native executable cache key omits imported source files even though the AOT pipeline now bundles file imports before code generation. Editing an imported module leaves the entry source hash unchanged, so try_execute_cached_native can run a stale executable containing the old imported code instead of rebuilding.
🧹 Additional findings from this change (not shown inline) (7)
  • [high] Exported cell accessors dereference arbitrary caller-supplied pointers without validating nullability, provenance, or the cell's allocation/type. For example, lkrt_rt_cell_get directly casts cell and dereferences it, so a C caller passing NULL (or a stale/foreign pointer) invokes undefined behavior rather than returning an ABI error or raising a controlled runtime error.
  • [high] The no_std runtime's global spin::Mutex&lt;RuntimeState&gt; can deadlock permanently when an interrupt arrives while normal code holds it. with_runtime locks with interrupts left enabled; the ISR dispatch path can call a compiled LK handler, and that handler can enter any arena operation (for example string/container registration or handle release), which tries to acquire the same non-reentrant lock on the interrupted core. The ISR spins forever and the interrupted code can never release the lock.
  • [medium] Calling set_global or get_global consumes the pending registry and makes later registration panic even though no eval has occurred, contradicting the documented registration contract and making initialization order observable.
  • [medium] The Tree-sitter grammar cannot parse the newly documented try expression form (and also has no defer or unsafe production), so Zed's parser/highlighting rejects current-language examples even though the compiler accepts them.
  • [medium] VS Code TextMate highlighting does not recognize the new defer and unsafe keywords: the keyword regex in the changed grammar omits both, while the compiler lexer defines defer and accepts unsafe { ... } (used by the committed bare-metal .lk examples). These tokens are therefore highlighted as ordinary identifiers/operators rather than language keywords.
  • [medium] The correctness harness invokes a Cargo integration test target that is not present in the workspace: cargo test -p lk-cli --test examples_differential_test appears in the changed workflow (and Makefile), but there is no cli/tests/examples_differential_test.rs in the repository. Every scheduled/push run reaching the examples corpus step therefore fails with Cargo's 'no test target named examples_differential_test' instead of testing the corpus.
  • [high] The new slice-view ownership contract is not implemented by the runtime: Receiver::ConstructsView tells the AOT scope-drop pass that a live window keeps its source list alive, but lkrt_lkslice_i64_new only stores the raw source pointer and allocates the window itself; it does not retain/reference-count the source. If the source list's scope ends while the window escapes (for example, return a window from a helper), scope-drop can release the source and later window reads dereference freed arena storage, producing a use-after-free. The ABI/lowering ownership decision and the lkrt view implementation therefore disagree across scopes.
♻️ Previously reported (still present) (27)
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.
  • [high] Dependency form-data@4.0.5 is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.
  • [high] Dependency js-yaml@4.1.1 is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the mailto: validator scan-loop on attacker text); upgrade to at least 5.0.2.
  • [high] Dependency tmp@0.2.5 is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.
  • [medium] Dependency js-yaml@4.1.1 is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.
  • [medium] Dependency markdown-it@14.1.1 is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.
  • [medium] Dependency qs@6.15.1 is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
  • [info] Dependency memmap2@0.2.3 is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate memmap2); upgrade to at least 0.9.11.
  • [info] Dependency bare-metal@0.2.5 is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
🗑️ Suppressed and duplicate diagnostics (1)
  • The defer walker/rewrite also omits Stmt::WhileLet, another supported control-flow statement. Consequently returns in a while let body do not receive pending releases, and a defer nested in that body is neither rejected nor rewritten. Stmt::WhileLet is a real parser/compiler AST variant, so this violates both reverse-release-on-every-return and the documented placement restriction; it would be false only if while let were excluded from all function bodies despite its AST/parser support.
🤖 Prompt for AI agents — all findings (37)
In core/src/stmt/defer.rs around line 144, address this finding:
Trait default methods containing `defer` are never desugared, so a `defer` survives into type checking/compilation after the promised pre-consumer rewrite. `desugar_defers` descends into functions and impl methods but has no `Stmt::Trait` arm; `expand_program_source` then copies trait defaults into impls after desugaring. A program such as `trait T { fn f() { defer release(); return 1; } } impl T for S {}` therefore copies a raw `Stmt::Defer` into the generated method (and even a trait default used nowhere remains unprocessed), violating the invariant that downstream consumers never see `Stmt::Defer`. This is introduced by combining trait-default expansion with the existing defer traversal; it would be false only if trait default bodies are guaranteed by the parser/macros never to contain `defer` or are rewritten elsewhere before checking.

In core/src/stmt/defer.rs around line 143, address this finding:
`if let` is omitted from both defer traversal and release insertion. With a top-level defer followed by `if let p = value { return ... }`, `with_releases` matches no `Stmt::IfLet` arm and leaves the nested return unreleased; with a defer written inside the `if let` body, `descend` also has no `IfLet` arm, so `reject_stray` never reports the forbidden placement and the raw defer reaches downstream consumers. This is a concrete missing branch variant in the AST rewrite and would be false only if `IfLet` were impossible in function bodies, which the parser and compiler explicitly support.

In core/src/val/runtime_model/equality.rs around line 163, address this finding:
Structural equality of objects bypasses the depth guard and can recurse until Rust stack overflow on deeply nested/cyclic structs.

In lkrt/src/panic.rs around line 295, address this finding:
Exported cell accessors dereference arbitrary caller-supplied pointers without validating nullability, provenance, or the cell's allocation/type. For example, `lkrt_rt_cell_get` directly casts `cell` and dereferences it, so a C caller passing NULL (or a stale/foreign pointer) invokes undefined behavior rather than returning an ABI error or raising a controlled runtime error.

In lkrt/src/state.rs around line 76, address this finding:
The no_std runtime's global `spin::Mutex<RuntimeState>` can deadlock permanently when an interrupt arrives while normal code holds it. `with_runtime` locks with interrupts left enabled; the ISR dispatch path can call a compiled LK handler, and that handler can enter any arena operation (for example string/container registration or handle release), which tries to acquire the same non-reentrant lock on the interrupted core. The ISR spins forever and the interrupted code can never release the lock.

In api/src/lib.rs around line 153, address this finding:
Calling `set_global` or `get_global` consumes the pending registry and makes later registration panic even though no `eval` has occurred, contradicting the documented registration contract and making initialization order observable.

In ecosystem/tree-sitter-lk/grammar.js around line 788, address this finding:
The Tree-sitter grammar cannot parse the newly documented `try` expression form (and also has no `defer` or `unsafe` production), so Zed's parser/highlighting rejects current-language examples even though the compiler accepts them.

In ecosystem/vsc-ext/lsp/syntaxes/lk.tmLanguage.json around line 516, address this finding:
VS Code TextMate highlighting does not recognize the new `defer` and `unsafe` keywords: the keyword regex in the changed grammar omits both, while the compiler lexer defines `defer` and accepts `unsafe { ... }` (used by the committed bare-metal `.lk` examples). These tokens are therefore highlighted as ordinary identifiers/operators rather than language keywords.

In .github/workflows/correctness.yml around line 67, address this finding:
The correctness harness invokes a Cargo integration test target that is not present in the workspace: `cargo test -p lk-cli --test examples_differential_test` appears in the changed workflow (and Makefile), but there is no `cli/tests/examples_differential_test.rs` in the repository. Every scheduled/push run reaching the examples corpus step therefore fails with Cargo's 'no test target named examples_differential_test' instead of testing the corpus.

In lkrt/src/lkslice.rs around line 90, address this finding:
The new slice-view ownership contract is not implemented by the runtime: `Receiver::ConstructsView` tells the AOT scope-drop pass that a live window keeps its source list alive, but `lkrt_lkslice_i64_new` only stores the raw source pointer and allocates the window itself; it does not retain/reference-count the source. If the source list's scope ends while the window escapes (for example, return a window from a helper), scope-drop can release the source and later window reads dereference freed arena storage, producing a use-after-free. The ABI/lowering ownership decision and the lkrt view implementation therefore disagree across scopes.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `form-data@4.0.5` is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the `mailto:` validator scan-loop on attacker text); upgrade to at least 5.0.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `tmp@0.2.5` is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `markdown-it@14.1.1` is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `qs@6.15.1` is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.

In Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.

In Cargo.lock, address this finding:
Dependency `memmap2@0.2.3` is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate `memmap2`); upgrade to at least 0.9.11.

In bare-metal/Cargo.lock, address this finding:
Dependency `bare-metal@0.2.5` is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.

In ecosystem/zed-ext/Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.
📜 Review details

Coverage

  • scopes: 5/7 complete

Comment thread core/src/stmt/defer.rs
}
}
Stmt::While { body, .. } | Stmt::For { body, .. } => descend(body),
Stmt::Impl { methods, .. } => {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟠 High

Trait default methods containing defer are never desugared, so a defer survives into type checking/compilation after the promised pre-consumer rewrite. desugar_defers descends into functions and impl methods but has no Stmt::Trait arm; expand_program_source then copies trait defaults into impls after desugaring. A program such as trait T { fn f() { defer release(); return 1; } } impl T for S {} therefore copies a raw Stmt::Defer into the generated method (and even a trait default used nowhere remains unprocessed), violating the invariant that downstream consumers never see Stmt::Defer. This is introduced by combining trait-default expansion with the existing defer traversal; it would be false only if trait default bodies are guaranteed by the parser/macros never to contain defer or are rewritten elsewhere before checking.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In core/src/stmt/defer.rs, address this finding:
Trait default methods containing `defer` are never desugared, so a `defer` survives into type checking/compilation after the promised pre-consumer rewrite. `desugar_defers` descends into functions and impl methods but has no `Stmt::Trait` arm; `expand_program_source` then copies trait defaults into impls after desugaring. A program such as `trait T { fn f() { defer release(); return 1; } } impl T for S {}` therefore copies a raw `Stmt::Defer` into the generated method (and even a trait default used nowhere remains unprocessed), violating the invariant that downstream consumers never see `Stmt::Defer`. This is introduced by combining trait-default expansion with the existing defer traversal; it would be false only if trait default bodies are guaranteed by the parser/macros never to contain `defer` or are rewritten elsewhere before checking.

To have the bot fix this, comment @winnowl fix.

Comment thread core/src/stmt/defer.rs
None => Ok(()),
}
}
Stmt::While { body, .. } | Stmt::For { body, .. } => descend(body),

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟠 High

if let is omitted from both defer traversal and release insertion. With a top-level defer followed by if let p = value { return ... }, with_releases matches no Stmt::IfLet arm and leaves the nested return unreleased; with a defer written inside the if let body, descend also has no IfLet arm, so reject_stray never reports the forbidden placement and the raw defer reaches downstream consumers. This is a concrete missing branch variant in the AST rewrite and would be false only if IfLet were impossible in function bodies, which the parser and compiler explicitly support.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In core/src/stmt/defer.rs, address this finding:
`if let` is omitted from both defer traversal and release insertion. With a top-level defer followed by `if let p = value { return ... }`, `with_releases` matches no `Stmt::IfLet` arm and leaves the nested return unreleased; with a defer written inside the `if let` body, `descend` also has no `IfLet` arm, so `reject_stray` never reports the forbidden placement and the raw defer reaches downstream consumers. This is a concrete missing branch variant in the AST rewrite and would be false only if `IfLet` were impossible in function bodies, which the parser and compiler explicitly support.

To have the bot fix this, comment @winnowl fix.

let Some(right_value) = right.fields.get(name) else {
return Ok(false);
};
if !self.values(left_value, right_value, depth + 1)? {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Data Integrity | 🟠 High

Structural equality of objects bypasses the depth guard and can recurse until Rust stack overflow on deeply nested/cyclic structs.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ I did not execute a reproducer, and the exact surface for constructing a cyclic object from source language code is not established; however, the equality routine can receive cyclic heap graphs and the deep-object case is directly reachable through the VM equality call sites.
🤖 Prompt for AI agents
In core/src/val/runtime_model/equality.rs, address this finding:
Structural equality of objects bypasses the depth guard and can recurse until Rust stack overflow on deeply nested/cyclic structs.

To have the bot fix this, comment @winnowl fix.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🔎 Confirmed findings (3)
  • high Defer statements in trait default method bodies are not desugared, so a default copied into an implementation does not release on returns/fall-through. For example, trait T { fn f() { defer cleanup(); return 1; } } impl T for S {} leaves the Stmt::Defer inside the trait's function because descend has no Stmt::Trait arm; the later apply_trait_defaults clones that still-unrewritten function into the impl. (inline)
  • high try_call cannot safely round-trip heap-valued error(...) from a RuntimeCallable: it clears the caller's pending root and returns the callee's raw heap handle into the caller heap. (inline)
  • medium Partial user-address-space construction leaks every page allocated before the first allocation failure. build_user_space allocates pml4, pdpt, directory, and table and immediately returns 0 when any is zero, without releasing the nonzero pages. On a low-memory machine (or after earlier task/device allocations), a failure at the third or fourth allocation permanently consumes the earlier page-table pages; the subsequent second user-task construction and later page/stack allocations can fail even though those pages are no longer reachable by any task. This violates the required resource-lifetime invariant and is introduced by the new dynamically allocated per-task address spaces. This would be false only if page_alloc were guaranteed to fail atomically without returning any earlier pages, or if some separately verified cleanup reclaimed these pages (neither is present). (inline)

⛔ Unresolved from previous review (26) — not approved until fixed

  • The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.
  • aot/lower/src/function.rs: return; inside an outlined try body does not propagate the enclosing-function return channel. The region scanner marks Opcode::Return0 as body_returns and the caller allocates/passes flag and value cells, but the body lowering only parks a return when matching Some(Exit::Ret(Some(reg))); Exit::Ret(None) bypasses that code and leaves the flag at its seeded false value. On the normal body-completion edge the caller therefore treats return; as a raised/catch outcome (or, for an all-return body, reaches the wrong continuation) instead of returning nil from the enclosing function. A minimal failure is try { return; } catch e { print("caught") } in a non-entry function: the VM returns immediately, while native lowering enters the catch path. This would be disproved if Return0 were converted to an Exit::Ret(Some(...)) before this match or if another lowering path sets the return flag for Ret(None), neither of which is present in the inspected code.
  • The Tier-1 hybrid bridge uses one module-global lk_hybrid_argbuf for every CallVm site, but the native runtime also supports concurrent spawned tasks. If two native threads execute a bridged call concurrently, each overwrites the shared tags/payloads before the other bridge reads them, so the VM function can receive another thread's arguments (or inconsistent tag/value pairs). The comment's single-threaded assumption is not enforced by the language/runtime.
  • cli/src/native_compile.rs: Native executable cache entries are not invalidated when the linked lkrt/runtime or toolchain changes, allowing an executable with an old ABI or runtime implementation to be reused for unchanged source.
  • aot/codegen/src/clif.rs: Tier 1 hybrid calls are not safe when native code executes concurrently: all generated CallVm sites share one writable global argument buffer, so threads can overwrite each other's tagged arguments before the bridge reads them.
  • core/src/stmt/defer.rs: Trait default methods containing defer are never desugared, so a defer survives into type checking/compilation after the promised pre-consumer rewrite. desugar_defers descends into functions and impl methods but has no Stmt::Trait arm; expand_program_source then copies trait defaults into impls after desugaring. A program such as trait T { fn f() { defer release(); return 1; } } impl T for S {} therefore copies a raw Stmt::Defer into the generated method (and even a trait default used nowhere remains unprocessed), violating the invariant that downstream consumers never see Stmt::Defer. This is introduced by combining trait-default expansion with the existing defer traversal; it would be false only if trait default bodies are guaranteed by the parser/macros never to contain defer or are rewritten elsewhere before checking.
  • core/src/val/runtime_model/equality.rs: Structural equality of nested objects bypasses the depth guard and can recurse until Rust stack overflow.
  • aot/codegen/src/clif.rs: The Tier-1 hybrid argument buffer is a single mutable module-global, but native spawn/go execute compiled callbacks on OS threads. Two concurrent hybrid calls (or a callback racing the parent) overwrite tags/values between marshaling and lk_hybrid_call_*, causing the VM to receive another call's arguments.
  • The native dynamic-value work adds Set and Bytes carrier tags (and documents them as values that can cross a bridged return), but the hybrid VM bridge still only marshals Nil/Bool/Int/Float/Str/List/Map: any VM function returning a Set or Bytes reaches the Some(other) => hybrid_die(...) arm and terminates instead of returning the new LkDyn. The public header likewise advertises tags only through MAP, so a Tier-1 hybrid caller cannot consume these values despite the runtime/ABI supporting them.
  • bare-metal-x86/src/user.rs: The ring-3 syscall trampoline enters the compiled dispatcher with a misaligned stack: after the CPU's ring transition, the seven pushes leave RSP 16-byte aligned, but sub rsp, 264 changes it to RSP%16 == 8 immediately before call lk_syscall_dispatch (the ABI requires the caller's RSP to be 0 mod 16 before a call). A dispatcher or callee that emits an aligned SSE stack spill can fault or misbehave on every syscall, so the user boundary is not ABI-correct; reserving 256 bytes (or otherwise explicitly aligning) would preserve the required phase. This is introduced by the new syscall trampoline; it would be disproved only if the actual native LK calling convention explicitly requires the opposite stack phase and all possible callees avoid alignment-sensitive spills.
  • core/src/stmt/defer.rs: defer inside if let/while let is neither rejected nor rewritten, so a deferred statement can reach downstream passes and returns in those bodies do not release pending resources.
  • Native execution can reuse stale cached binaries after an imported module changes.
  • core/src/val/runtime_model/equality.rs: Structural equality does not enforce MAX_VALUE_DEPTH for object-field recursion, so deeply nested or cyclic object graphs can overflow the Rust stack instead of returning the documented comparison error.
  • aot/lower/src/function.rs: A try body that returns can exceed the trampoline's fixed arity even though lowering accepts it, causing the generated native program to trap instead of executing the VM semantics.
  • aot/codegen/src/clif.rs: The hybrid bridge's shared argument buffer is not safe for concurrent native execution: two native threads can overwrite one another's tagged slots and payloads between marshaling and lk_hybrid_call_*, so the VM receives a mixed argument vector.
  • api/src/lib.rs: The hybrid return bridge advertises the same LkDyn carrier and DYN_LIST/DYN_MAP tags as lkrt, but its container-return path is only wired for the injected list/map constructors and cannot marshal typed string lists (it unconditionally dies for TypedList::String). Consequently a VM function returning a valid string list can be selected by the AOT hybrid lowering and then terminate the native process at the bridge, while the VM and ordinary native stdlib paths return it normally.
  • aot/codegen/src/clif.rs: Hybrid bridge calls are not isolated when native tasks run concurrently: every generated function shares one mutable lk_hybrid_argbuf, so two spawned threads can overwrite each other's tagged arguments between the stores and lk_hybrid_call_*. For example, two spawn0/spawn1 callbacks containing CallVm can race: thread A writes argument 1, thread B writes argument 2, then A invokes the VM with B's value (and vice versa), producing nondeterministic results despite the runtime explicitly creating OS threads. The comment's single-thread assumption is contradicted by the exported spawn implementation, and the bridge's VM mutex only serializes after arguments have already been read. This would be false only if the native lowering forbade any CallVm execution from spawned functions or otherwise serialized all bridge callers, but the runtime exposes spawn for compiled callbacks and no such synchronization is present.
  • core/src/val/runtime_model/equality.rs: Structural equality does not enforce the configured depth limit for nested structs, so cyclic or sufficiently deep object graphs can recurse until a Rust stack overflow instead of returning the documented comparison error.
  • api/src/lib.rs: Vm::eval_value can overflow the host stack instead of returning an error for cyclic VM containers.
  • The native executable cache key omits imported source contents even though this change now bundles file imports during native compilation, so editing an imported .lk file can silently execute stale cached code.
  • core/src/val/runtime_model/equality.rs: Canonical structural equality does not enforce its depth limit for object fields. In Comparison::objects, each field is compared with self.values(left_value, right_value, depth + 1) instead of self.nested(...); therefore a chain of deeply nested structs (or a cyclic object graph if one is constructed through runtime mutation) bypasses the MAX_VALUE_DEPTH check and recurses through Rust stack frames until stack overflow, violating the stated no-process-abort comparison limit. The claim is false only if object graphs are proven impossible to nest beyond the limit/cycle in every supported runtime path, or if object equality is intentionally exempt from the depth contract.
  • lkrt/src/chan.rs: An uncaught runtime raise in a spawned task terminates the whole native process instead of becoming a failed task that task.await can catch/report.
  • lk bundle does not produce a self-contained executable for programs with file or package imports, despite the command and output path documenting a self-contained bundle.
  • The native executable cache key omits imported source files even though the AOT pipeline now bundles file imports before code generation. Editing an imported module leaves the entry source hash unchanged, so try_execute_cached_native can run a stale executable containing the old imported code instead of rebuilding.
  • core/src/stmt/defer.rs: if let is omitted from both defer traversal and release insertion. With a top-level defer followed by if let p = value { return ... }, with_releases matches no Stmt::IfLet arm and leaves the nested return unreleased; with a defer written inside the if let body, descend also has no IfLet arm, so reject_stray never reports the forbidden placement and the raw defer reaches downstream consumers. This is a concrete missing branch variant in the AST rewrite and would be false only if IfLet were impossible in function bodies, which the parser and compiler explicitly support.
  • core/src/val/runtime_model/equality.rs: Structural equality of objects bypasses the depth guard and can recurse until Rust stack overflow on deeply nested/cyclic structs.
🧹 Additional findings from this change (not shown inline) (9)
  • [high] A defer at module/program scope is silently rewritten instead of rejected as unsupported placement. desugar_defers calls rewrite_sequence directly on program.statements, so a source such as defer cleanup(); let x = 1; removes the defer and injects cleanup() before the later top-level statement (or executes it at module initialization if it is the only statement), even though the documented feature is only valid in a function body.
  • [high] The synthetic return binding __lk_defer_return can capture names used by a defer release. A user can legally bind that identifier, then write defer use(__lk_defer_return); return value;; with_releases inserts a new let __lk_defer_return = value before the release, so the release resolves to the returned value rather than the user's preexisting resource/value, changing lexical scope and evaluation semantics.
  • [high] Native-call message raises can consume an ancestor handler while the callee frame is still active, corrupting frame/register state instead of unwinding to that handler. handle_call_error dispatches LanguageRaise directly to handle_language_raise, whose implementation unconditionally pops the last handler without checking handler.frame_base == self.frame_base; if a flattened LK callee has no local try but its caller does, the ancestor entry is popped, enter_handler sets frame_base to the ancestor yet writes catch_reg before the caller frame/stack window is restored. This can bind the wrong slot or fail with an out-of-bounds/incorrect catch result for try { f() } catch e where f calls a native error("x") through a nested call path. The change introduced/exposed this through flattened multi-frame execution and the direct call-error path. This would be false if every handle_call_error invocation were proven to run only with a handler belonging to the current frame (or if the handler stack were always truncated/restored before it is called), but the flat frame design explicitly retains ancestor handlers and handle_language_raise has no such guard.
  • [high] Outlined try-region bodies keep jump operands encoded for the parent function's program-counter layout, so any conditional/unconditional jump inside the body targets the wrong instruction after outlining.
  • [medium] The ASan hybrid harness still links the nightly-built lkrt against the ordinary-toolchain lk-api archive instead of the matching nightly archive it just built.
  • [medium] The EDU interrupt probe leaves the device and PIC state live on its timeout/failure path. After pic_unmask(irq) and edu_raise, task_wait_on can return because its deadline expires without lk_edu_isr running; the code then only calls pic_mask_line(irq). It never disables the EDU interrupt source or reads/acknowledges its pending status on this path. A later probe or another device sharing/reusing that IRQ can therefore inherit a still-asserted/pending EDU request; when the line is unmasked, the stale request can invoke the newly installed handler or create a spurious/in-service condition unrelated to the operation that enabled it. The success path does clear the device in the ISR, but the ordinary failure path explicitly does not. This would be false only if the EDU device guaranteed that edu_raise cannot leave a pending interrupt whenever the wait times out, a guarantee not established by the driver or caller.
  • [medium] The tree-sitter editor grammar does not recognize newly supported defer statements or try { ... } catch ... expressions, so tree-sitter-based editors will report errors/malformed trees for the shipped examples even though the core lexer/parser accepts them. The grammar only includes try_statement as a statement and has no defer rule (and defer is absent from the keyword set); examples/syntax/defer.lk and examples/syntax/try_expression.lk exercise both forms. This is introduced by the scope's grammar update because the generated artifacts are synchronized to this incomplete grammar, not to the core syntax. It would be disproven if the generated tree-sitter parser accepted those examples (with no error nodes) or if another included rule covered defer and expression-position try.
  • [high] The Tier-1 hybrid return bridge cannot transport several valid VM values even though the VM/AOT dynamic ABI now defines them as first-class values. marshal_value handles only String, List, and Map heap objects; Set, Bytes, Slice, structs, and other heap values take the fatal bridged return kind not yet marshalable path. Thus a VM-executed function returning (or raising) a valid Set/Bytes/Slice works in the VM but terminates a hybrid native binary instead of returning/re-raising the value. This is especially inconsistent with the adjacent lkrt ABI, which explicitly added DYN_SET, DYN_BYTES, and DYN_SLICE tags and implements their display/equality/boxing paths.
  • [medium] The hybrid bridge's map contract is narrower than the language/runtime map contract: marshal_map rejects every non-string key, while VM maps legally support integer (and other RuntimeMapKey) keys and the native dynamic ABI has generalized map tags/operations. A VM function that returns a valid {1: value} map, or raises that map into a native try frame, therefore dies in lk-api with map with non-string key rather than producing the equivalent native value or catchable error. The C header describes lk_hybrid_call_r as returning an LkDyn-shaped VM result without documenting this semantic restriction.
♻️ Previously reported (still present) (27)
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.
  • [high] Dependency form-data@4.0.5 is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.
  • [high] Dependency js-yaml@4.1.1 is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the mailto: validator scan-loop on attacker text); upgrade to at least 5.0.2.
  • [high] Dependency tmp@0.2.5 is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.
  • [medium] Dependency js-yaml@4.1.1 is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.
  • [medium] Dependency markdown-it@14.1.1 is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.
  • [medium] Dependency qs@6.15.1 is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
  • [info] Dependency memmap2@0.2.3 is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate memmap2); upgrade to at least 0.9.11.
  • [info] Dependency bare-metal@0.2.5 is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
🗑️ Suppressed and duplicate diagnostics (1)
  • outline copies the body's raw bytecode without rebasing relative jump operands. cfg::rel defines jump targets as pc + 1 + offset; after slicing [body_start..body_end], the same offset is applied from the new (zero-based) body PC, so a jump/branch in a try body lands at a different instruction (or out of range). For example, a body jump from parent pc 20 to parent pc 24 has offset 3, but at outlined pc 0 offset 3 targets pc 4 rather than the sliced instruction corresponding to parent pc 24. This changes loop/if behavior or makes lowering reject valid bodies. This is false only if the bytecode representation stores jump offsets relative to the enclosing function-independent instruction identity, contrary to cfg::rel and VM relative-target helpers, or if try-body control flow is guaranteed never to contain jumps (which the scanner explicitly validates rather than forbids).
🤖 Prompt for AI agents — all findings (39)
In core/src/stmt/defer.rs around line 104, address this finding:
A `defer` at module/program scope is silently rewritten instead of rejected as unsupported placement. `desugar_defers` calls `rewrite_sequence` directly on `program.statements`, so a source such as `defer cleanup(); let x = 1;` removes the defer and injects `cleanup()` before the later top-level statement (or executes it at module initialization if it is the only statement), even though the documented feature is only valid in a function body.

In core/src/stmt/defer.rs around line 118, address this finding:
Defer statements in trait default method bodies are not desugared, so a default copied into an implementation does not release on returns/fall-through. For example, `trait T { fn f() { defer cleanup(); return 1; } } impl T for S {}` leaves the `Stmt::Defer` inside the trait's function because `descend` has no `Stmt::Trait` arm; the later `apply_trait_defaults` clones that still-unrewritten function into the impl.

In core/src/stmt/defer.rs around line 101, address this finding:
The synthetic return binding `__lk_defer_return` can capture names used by a defer release. A user can legally bind that identifier, then write `defer use(__lk_defer_return); return value;`; `with_releases` inserts a new `let __lk_defer_return = value` before the release, so the release resolves to the returned value rather than the user's preexisting resource/value, changing lexical scope and evaluation semantics.

In core/src/vm/exec/handler.rs around line 176, address this finding:
Native-call message raises can consume an ancestor handler while the callee frame is still active, corrupting frame/register state instead of unwinding to that handler. `handle_call_error` dispatches `LanguageRaise` directly to `handle_language_raise`, whose implementation unconditionally pops the last handler without checking `handler.frame_base == self.frame_base`; if a flattened LK callee has no local try but its caller does, the ancestor entry is popped, `enter_handler` sets `frame_base` to the ancestor yet writes `catch_reg` before the caller frame/stack window is restored. This can bind the wrong slot or fail with an out-of-bounds/incorrect catch result for `try { f() } catch e` where `f` calls a native `error("x")` through a nested call path. The change introduced/exposed this through flattened multi-frame execution and the direct call-error path. This would be false if every `handle_call_error` invocation were proven to run only with a handler belonging to the current frame (or if the handler stack were always truncated/restored before it is called), but the flat frame design explicitly retains ancestor handlers and `handle_language_raise` has no such guard.

In aot/lower/src/try_region.rs around line 139, address this finding:
Outlined try-region bodies keep jump operands encoded for the parent function's program-counter layout, so any conditional/unconditional jump inside the body targets the wrong instruction after outlining.

In stdlib/common/src/language.rs around line 80, address this finding:
`try_call` cannot safely round-trip heap-valued `error(...)` from a `RuntimeCallable`: it clears the caller's pending root and returns the callee's raw heap handle into the caller heap.

In .github/workflows/correctness.yml around line 97, address this finding:
The ASan hybrid harness still links the nightly-built `lkrt` against the ordinary-toolchain `lk-api` archive instead of the matching nightly archive it just built.

In bare-metal-x86/program.lk around line 2811, address this finding:
Partial user-address-space construction leaks every page allocated before the first allocation failure. `build_user_space` allocates pml4, pdpt, directory, and table and immediately returns 0 when any is zero, without releasing the nonzero pages. On a low-memory machine (or after earlier task/device allocations), a failure at the third or fourth allocation permanently consumes the earlier page-table pages; the subsequent second user-task construction and later page/stack allocations can fail even though those pages are no longer reachable by any task. This violates the required resource-lifetime invariant and is introduced by the new dynamically allocated per-task address spaces. This would be false only if `page_alloc` were guaranteed to fail atomically without returning any earlier pages, or if some separately verified cleanup reclaimed these pages (neither is present).

In bare-metal-x86/program.lk, address this finding:
The EDU interrupt probe leaves the device and PIC state live on its timeout/failure path. After `pic_unmask(irq)` and `edu_raise`, `task_wait_on` can return because its deadline expires without `lk_edu_isr` running; the code then only calls `pic_mask_line(irq)`. It never disables the EDU interrupt source or reads/acknowledges its pending status on this path. A later probe or another device sharing/reusing that IRQ can therefore inherit a still-asserted/pending EDU request; when the line is unmasked, the stale request can invoke the newly installed handler or create a spurious/in-service condition unrelated to the operation that enabled it. The success path does clear the device in the ISR, but the ordinary failure path explicitly does not. This would be false only if the EDU device guaranteed that `edu_raise` cannot leave a pending interrupt whenever the wait times out, a guarantee not established by the driver or caller.

In ecosystem/tree-sitter-lk/grammar.js around line 788, address this finding:
The tree-sitter editor grammar does not recognize newly supported `defer` statements or `try { ... } catch ...` expressions, so tree-sitter-based editors will report errors/malformed trees for the shipped examples even though the core lexer/parser accepts them. The grammar only includes `try_statement` as a statement and has no `defer` rule (and `defer` is absent from the keyword set); `examples/syntax/defer.lk` and `examples/syntax/try_expression.lk` exercise both forms. This is introduced by the scope's grammar update because the generated artifacts are synchronized to this incomplete grammar, not to the core syntax. It would be disproven if the generated tree-sitter parser accepted those examples (with no error nodes) or if another included rule covered `defer` and expression-position `try`.

In api/src/lib.rs, address this finding:
The Tier-1 hybrid return bridge cannot transport several valid VM values even though the VM/AOT dynamic ABI now defines them as first-class values. `marshal_value` handles only String, List, and Map heap objects; Set, Bytes, Slice, structs, and other heap values take the fatal `bridged return kind not yet marshalable` path. Thus a VM-executed function returning (or raising) a valid `Set`/`Bytes`/`Slice` works in the VM but terminates a hybrid native binary instead of returning/re-raising the value. This is especially inconsistent with the adjacent `lkrt` ABI, which explicitly added DYN_SET, DYN_BYTES, and DYN_SLICE tags and implements their display/equality/boxing paths.

In api/src/lib.rs, address this finding:
The hybrid bridge's map contract is narrower than the language/runtime map contract: `marshal_map` rejects every non-string key, while VM maps legally support integer (and other RuntimeMapKey) keys and the native dynamic ABI has generalized map tags/operations. A VM function that returns a valid `{1: value}` map, or raises that map into a native try frame, therefore dies in `lk-api` with `map with non-string key` rather than producing the equivalent native value or catchable error. The C header describes `lk_hybrid_call_r` as returning an LkDyn-shaped VM result without documenting this semantic restriction.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `form-data@4.0.5` is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the `mailto:` validator scan-loop on attacker text); upgrade to at least 5.0.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `tmp@0.2.5` is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `markdown-it@14.1.1` is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `qs@6.15.1` is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.

In Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.

In Cargo.lock, address this finding:
Dependency `memmap2@0.2.3` is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate `memmap2`); upgrade to at least 0.9.11.

In bare-metal/Cargo.lock, address this finding:
Dependency `bare-metal@0.2.5` is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.

In ecosystem/zed-ext/Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.
📜 Review details

Coverage

  • scopes: 6/7 complete

Comment thread core/src/stmt/defer.rs
/// function it is written in: a function nested inside one that defers something
/// has its own way out, and running the outer function's releases when the inner
/// one returns would release things the outer one is still using.
fn descend(stmt: &mut Stmt) -> Result<(), String> {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟠 High

Defer statements in trait default method bodies are not desugared, so a default copied into an implementation does not release on returns/fall-through. For example, trait T { fn f() { defer cleanup(); return 1; } } impl T for S {} leaves the Stmt::Defer inside the trait's function because descend has no Stmt::Trait arm; the later apply_trait_defaults clones that still-unrewritten function into the impl.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In core/src/stmt/defer.rs, address this finding:
Defer statements in trait default method bodies are not desugared, so a default copied into an implementation does not release on returns/fall-through. For example, `trait T { fn f() { defer cleanup(); return 1; } } impl T for S {}` leaves the `Stmt::Defer` inside the trait's function because `descend` has no `Stmt::Trait` arm; the later `apply_trait_defaults` clones that still-unrewritten function into the impl.

To have the bot fix this, comment @winnowl fix.

Comment thread stdlib/common/src/language.rs Outdated
return Err(anyhow!("try$call expects at least 1 argument: the function to call"));
};
let call_args = call_args.to_vec();
let outcome = {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Resource Lifetime | 🟠 High

try_call cannot safely round-trip heap-valued error(...) from a RuntimeCallable: it clears the caller's pending root and returns the callee's raw heap handle into the caller heap.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ I did not execute a cross-module regression test; the behavior follows directly from the distinct caller and RuntimeCallable state/heap paths.
🤖 Prompt for AI agents
In stdlib/common/src/language.rs, address this finding:
`try_call` cannot safely round-trip heap-valued `error(...)` from a `RuntimeCallable`: it clears the caller's pending root and returns the callee's raw heap handle into the caller heap.

To have the bot fix this, comment @winnowl fix.

Comment thread bare-metal-x86/program.lk
let pdpt = page_alloc(SHARED_PAGES);
let directory = page_alloc(SHARED_PAGES);
let table = page_alloc(SHARED_PAGES);
if (pml4 == 0 || pdpt == 0 || directory == 0 || table == 0) {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Resource Lifetime | 🟡 Medium

Partial user-address-space construction leaks every page allocated before the first allocation failure. build_user_space allocates pml4, pdpt, directory, and table and immediately returns 0 when any is zero, without releasing the nonzero pages. On a low-memory machine (or after earlier task/device allocations), a failure at the third or fourth allocation permanently consumes the earlier page-table pages; the subsequent second user-task construction and later page/stack allocations can fail even though those pages are no longer reachable by any task. This violates the required resource-lifetime invariant and is introduced by the new dynamically allocated per-task address spaces. This would be false only if page_alloc were guaranteed to fail atomically without returning any earlier pages, or if some separately verified cleanup reclaimed these pages (neither is present).

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ I did not execute the bare-metal image, so the exact frequency of low-memory startup states is untested; the allocator semantics and failure path are directly established in the source.
🤖 Prompt for AI agents
In bare-metal-x86/program.lk, address this finding:
Partial user-address-space construction leaks every page allocated before the first allocation failure. `build_user_space` allocates pml4, pdpt, directory, and table and immediately returns 0 when any is zero, without releasing the nonzero pages. On a low-memory machine (or after earlier task/device allocations), a failure at the third or fourth allocation permanently consumes the earlier page-table pages; the subsequent second user-task construction and later page/stack allocations can fail even though those pages are no longer reachable by any task. This violates the required resource-lifetime invariant and is introduced by the new dynamically allocated per-task address spaces. This would be false only if `page_alloc` were guaranteed to fail atomically without returning any earlier pages, or if some separately verified cleanup reclaimed these pages (neither is present).

To have the bot fix this, comment @winnowl fix.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🔎 Confirmed findings (3)
  • high Structural equality of objects is not depth-bounded, so cyclic or sufficiently deep object graphs can recurse until Rust stack overflow instead of returning the documented comparison error. (inline)
  • medium Generated stdlib signatures do not enforce the declared type of optional parameters, so invalid calls pass type checking and only fail in the native implementation. In constrain_stdlib_argument the param.optional early return skips assignability entirely, even though param.ty is the actual declared type (for example pad?: String, end?: Int, or min?: Int). Thus a call such as use string; string.pad_left("x", 3, 4) or use math; math.clamp(1, min: "bad") is accepted by the checker but reaches the runtime wrapper and raises a misleading native argument error. This was introduced by the generated-signature path (the old fallback did not claim these declarations); it would be disproven only if optional arguments were intentionally unconstrained by the public API, contrary to the generated signature/type documentation and runtime checks. (inline)
  • medium The AOT driver builds lk-api-cabi through Cargo but then consumes a hard-coded workspace/target/release/liblk_api_cabi.a; when a caller sets Cargo's supported CARGO_TARGET_DIR (or uses a configured target directory), Cargo writes the archive elsewhere and the driver still passes the nonexistent hard-coded path to the linker, so hybrid/Tier-0 compilation fails despite the producer build succeeding. (inline)

⛔ Unresolved from previous review (28) — not approved until fixed

  • The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.
  • core/src/stmt/defer.rs: Defer statements in trait default method bodies are not desugared, so a default copied into an implementation does not release on returns/fall-through. For example, trait T { fn f() { defer cleanup(); return 1; } } impl T for S {} leaves the Stmt::Defer inside the trait's function because descend has no Stmt::Trait arm; the later apply_trait_defaults clones that still-unrewritten function into the impl.
  • aot/lower/src/function.rs: return; inside an outlined try body does not propagate the enclosing-function return channel. The region scanner marks Opcode::Return0 as body_returns and the caller allocates/passes flag and value cells, but the body lowering only parks a return when matching Some(Exit::Ret(Some(reg))); Exit::Ret(None) bypasses that code and leaves the flag at its seeded false value. On the normal body-completion edge the caller therefore treats return; as a raised/catch outcome (or, for an all-return body, reaches the wrong continuation) instead of returning nil from the enclosing function. A minimal failure is try { return; } catch e { print("caught") } in a non-entry function: the VM returns immediately, while native lowering enters the catch path. This would be disproved if Return0 were converted to an Exit::Ret(Some(...)) before this match or if another lowering path sets the return flag for Ret(None), neither of which is present in the inspected code.
  • The Tier-1 hybrid bridge uses one module-global lk_hybrid_argbuf for every CallVm site, but the native runtime also supports concurrent spawned tasks. If two native threads execute a bridged call concurrently, each overwrites the shared tags/payloads before the other bridge reads them, so the VM function can receive another thread's arguments (or inconsistent tag/value pairs). The comment's single-threaded assumption is not enforced by the language/runtime.
  • cli/src/native_compile.rs: Native executable cache entries are not invalidated when the linked lkrt/runtime or toolchain changes, allowing an executable with an old ABI or runtime implementation to be reused for unchanged source.
  • aot/codegen/src/clif.rs: Tier 1 hybrid calls are not safe when native code executes concurrently: all generated CallVm sites share one writable global argument buffer, so threads can overwrite each other's tagged arguments before the bridge reads them.
  • core/src/stmt/defer.rs: Trait default methods containing defer are never desugared, so a defer survives into type checking/compilation after the promised pre-consumer rewrite. desugar_defers descends into functions and impl methods but has no Stmt::Trait arm; expand_program_source then copies trait defaults into impls after desugaring. A program such as trait T { fn f() { defer release(); return 1; } } impl T for S {} therefore copies a raw Stmt::Defer into the generated method (and even a trait default used nowhere remains unprocessed), violating the invariant that downstream consumers never see Stmt::Defer. This is introduced by combining trait-default expansion with the existing defer traversal; it would be false only if trait default bodies are guaranteed by the parser/macros never to contain defer or are rewritten elsewhere before checking.
  • core/src/val/runtime_model/equality.rs: Structural equality of nested objects bypasses the depth guard and can recurse until Rust stack overflow.
  • aot/codegen/src/clif.rs: The Tier-1 hybrid argument buffer is a single mutable module-global, but native spawn/go execute compiled callbacks on OS threads. Two concurrent hybrid calls (or a callback racing the parent) overwrite tags/values between marshaling and lk_hybrid_call_*, causing the VM to receive another call's arguments.
  • The native dynamic-value work adds Set and Bytes carrier tags (and documents them as values that can cross a bridged return), but the hybrid VM bridge still only marshals Nil/Bool/Int/Float/Str/List/Map: any VM function returning a Set or Bytes reaches the Some(other) => hybrid_die(...) arm and terminates instead of returning the new LkDyn. The public header likewise advertises tags only through MAP, so a Tier-1 hybrid caller cannot consume these values despite the runtime/ABI supporting them.
  • bare-metal-x86/src/user.rs: The ring-3 syscall trampoline enters the compiled dispatcher with a misaligned stack: after the CPU's ring transition, the seven pushes leave RSP 16-byte aligned, but sub rsp, 264 changes it to RSP%16 == 8 immediately before call lk_syscall_dispatch (the ABI requires the caller's RSP to be 0 mod 16 before a call). A dispatcher or callee that emits an aligned SSE stack spill can fault or misbehave on every syscall, so the user boundary is not ABI-correct; reserving 256 bytes (or otherwise explicitly aligning) would preserve the required phase. This is introduced by the new syscall trampoline; it would be disproved only if the actual native LK calling convention explicitly requires the opposite stack phase and all possible callees avoid alignment-sensitive spills.
  • core/src/stmt/defer.rs: defer inside if let/while let is neither rejected nor rewritten, so a deferred statement can reach downstream passes and returns in those bodies do not release pending resources.
  • Native execution can reuse stale cached binaries after an imported module changes.
  • core/src/val/runtime_model/equality.rs: Structural equality does not enforce MAX_VALUE_DEPTH for object-field recursion, so deeply nested or cyclic object graphs can overflow the Rust stack instead of returning the documented comparison error.
  • aot/lower/src/function.rs: A try body that returns can exceed the trampoline's fixed arity even though lowering accepts it, causing the generated native program to trap instead of executing the VM semantics.
  • aot/codegen/src/clif.rs: The hybrid bridge's shared argument buffer is not safe for concurrent native execution: two native threads can overwrite one another's tagged slots and payloads between marshaling and lk_hybrid_call_*, so the VM receives a mixed argument vector.
  • api/src/lib.rs: The hybrid return bridge advertises the same LkDyn carrier and DYN_LIST/DYN_MAP tags as lkrt, but its container-return path is only wired for the injected list/map constructors and cannot marshal typed string lists (it unconditionally dies for TypedList::String). Consequently a VM function returning a valid string list can be selected by the AOT hybrid lowering and then terminate the native process at the bridge, while the VM and ordinary native stdlib paths return it normally.
  • aot/codegen/src/clif.rs: Hybrid bridge calls are not isolated when native tasks run concurrently: every generated function shares one mutable lk_hybrid_argbuf, so two spawned threads can overwrite each other's tagged arguments between the stores and lk_hybrid_call_*. For example, two spawn0/spawn1 callbacks containing CallVm can race: thread A writes argument 1, thread B writes argument 2, then A invokes the VM with B's value (and vice versa), producing nondeterministic results despite the runtime explicitly creating OS threads. The comment's single-thread assumption is contradicted by the exported spawn implementation, and the bridge's VM mutex only serializes after arguments have already been read. This would be false only if the native lowering forbade any CallVm execution from spawned functions or otherwise serialized all bridge callers, but the runtime exposes spawn for compiled callbacks and no such synchronization is present.
  • core/src/val/runtime_model/equality.rs: Structural equality does not enforce the configured depth limit for nested structs, so cyclic or sufficiently deep object graphs can recurse until a Rust stack overflow instead of returning the documented comparison error.
  • api/src/lib.rs: Vm::eval_value can overflow the host stack instead of returning an error for cyclic VM containers.
  • The native executable cache key omits imported source contents even though this change now bundles file imports during native compilation, so editing an imported .lk file can silently execute stale cached code.
  • core/src/val/runtime_model/equality.rs: Canonical structural equality does not enforce its depth limit for object fields. In Comparison::objects, each field is compared with self.values(left_value, right_value, depth + 1) instead of self.nested(...); therefore a chain of deeply nested structs (or a cyclic object graph if one is constructed through runtime mutation) bypasses the MAX_VALUE_DEPTH check and recurses through Rust stack frames until stack overflow, violating the stated no-process-abort comparison limit. The claim is false only if object graphs are proven impossible to nest beyond the limit/cycle in every supported runtime path, or if object equality is intentionally exempt from the depth contract.
  • lkrt/src/chan.rs: An uncaught runtime raise in a spawned task terminates the whole native process instead of becoming a failed task that task.await can catch/report.
  • lk bundle does not produce a self-contained executable for programs with file or package imports, despite the command and output path documenting a self-contained bundle.
  • The native executable cache key omits imported source files even though the AOT pipeline now bundles file imports before code generation. Editing an imported module leaves the entry source hash unchanged, so try_execute_cached_native can run a stale executable containing the old imported code instead of rebuilding.
  • core/src/stmt/defer.rs: if let is omitted from both defer traversal and release insertion. With a top-level defer followed by if let p = value { return ... }, with_releases matches no Stmt::IfLet arm and leaves the nested return unreleased; with a defer written inside the if let body, descend also has no IfLet arm, so reject_stray never reports the forbidden placement and the raw defer reaches downstream consumers. This is a concrete missing branch variant in the AST rewrite and would be false only if IfLet were impossible in function bodies, which the parser and compiler explicitly support.
  • core/src/val/runtime_model/equality.rs: Structural equality of objects bypasses the depth guard and can recurse until Rust stack overflow on deeply nested/cyclic structs.
  • stdlib/common/src/language.rs: try_call cannot safely round-trip heap-valued error(...) from a RuntimeCallable: it clears the caller's pending root and returns the callee's raw heap handle into the caller heap.
🧹 Additional findings from this change (not shown inline) (9)
  • [medium] Annotated positional parameters with defaults cannot be parsed, even though the function parser explicitly supports positional defaults. In parse_function_stmt, after consuming :, parse_inline_type_until_param_delim() is used, but that collector does not stop at a top-level Token::Assign; it consumes = default_expr along with the type, so fn f(x: Int = 1) { ... } is rendered as something like Int = 1 and rejected as an invalid type. This is disproven only if the intended grammar forbids typed positional defaults (contradicting the parser's default-handling branch and the documented/defaulted-parameter obligation) or if another earlier parser path intercepts this spelling.
  • [medium] Declared stdlib parameter types are silently ignored for any optional parameter when that parameter is supplied positionally. Both direct positional checking and named checking call constrain_stdlib_argument, whose first branch returns immediately for param.optional, so a signature such as an optional Int accepts a supplied String instead of enforcing the declared type. The same behavior is present in the positional loop's if param.optional ... { continue; }. This is disproven only if optional is deliberately documented to mean 'untyped whenever present' rather than merely 'may be omitted'; the signature comments describe it as omission eligibility, and the richer-signature obligation requires supplied arguments to honor their declared types.
  • [medium] A defer inside a trait default method is not rewritten before the trait default is copied into implementations. desugar_defers only descends through functions, impl methods, blocks, conditionals, loops, and try expressions; it has no Stmt::Trait arm to visit Trait.default_methods. The later apply_trait_defaults clones those default method ASTs into Impl.methods, leaving Stmt::Defer in the generated method for the resolver/type checker/compiler, contrary to the invariant that defer is erased after macros and before all downstream consumers. This is disproven only if trait default method bodies are syntactically forbidden from containing defer, but the parser's ordinary function-body grammar and the documented defer obligation do not establish such a restriction.
  • [high] The artifact verifier accepts functions whose param_count exceeds register_count, but flattened call setup copies every argument to new_base + i after allocating only register_count slots. A crafted artifact (or a module assembled by a host) with a 1-register, 2-parameter callee can therefore overwrite the next frame/stack region or panic when called, rather than being rejected at load time.
  • [high] The stack-exhaustion guard is not safe for the OS-thread concurrency model: it installs one process-wide SIGSEGV/SIGBUS handler but computes a single address interval from the main thread's stack and installs the alternate stack only on the calling thread. A spawned task that exhausts its own stack therefore faults outside lk_stack_low..lk_stack_high, the handler restores the default disposition and returns, and the process dies with an undiagnosed SIGSEGV/SIGBUS (and the child has no alternate signal stack, so the handler may itself be unable to run). This violates the promised diagnosed stack-guard outcome for concurrent native tasks. The claim would be false only if spawned tasks were guaranteed never to recurse/overflow or every spawned thread independently called lk_install_stack_guard with per-thread stack bounds/alternate storage.
  • [medium] The generated type-signature registry silently overwrites conflicting declarations instead of rejecting them. register_stdlib_signatures calls HashMap::insert for every path without comparing an existing entry, so registering two modules/metadata tables that both declare math.abs differently leaves whichever table registered last as the checker truth, while the parallel StdlibMetadataRegistry reports a conflict. This can make registration order change accepted argument types/return types and violates the obligation that metadata conflicts be reported consistently. The claim would be false only if this registry were guaranteed never to receive conflicting public inputs, but the function is public and the metadata layer explicitly treats duplicate paths as a conflict.
  • [medium] build_user_space leaks every page it allocated when any later page allocation fails (and callers also cannot reclaim a successfully built address space if spawn_user_task fails). The four allocations are performed into locals and the failure branch immediately returns 0 without pages_release_run/release_page. On a near-exhausted page arena, e.g. the first three allocations succeed and the fourth returns 0, those three pages remain counted as used and are unavailable forever; repeated user-space creation or other device/task allocations then fail despite the failed space never being live. This violates the page/user-address-space lifetime invariant.
  • [medium] The editor grammars do not recognize the newly supported defer statement, so Tree-sitter/VS Code syntax support is not compatible with the language parser.
  • [high] The native execution cache can reuse a binary built with different AOT orchestration settings, including pure-native versus hybrid mode.
♻️ Previously reported (still present) (27)
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.
  • [high] Dependency form-data@4.0.5 is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.
  • [high] Dependency js-yaml@4.1.1 is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the mailto: validator scan-loop on attacker text); upgrade to at least 5.0.2.
  • [high] Dependency tmp@0.2.5 is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.
  • [medium] Dependency js-yaml@4.1.1 is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.
  • [medium] Dependency markdown-it@14.1.1 is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.
  • [medium] Dependency qs@6.15.1 is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
  • [info] Dependency memmap2@0.2.3 is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate memmap2); upgrade to at least 0.9.11.
  • [info] Dependency bare-metal@0.2.5 is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
🗑️ Suppressed and duplicate diagnostics (1)
  • VS Code runtime configuration changes silently change semantic-token throttling from the configured/default 120ms to 40ms.
🤖 Prompt for AI agents — all findings (39)
In core/src/stmt/stmt_parser/helpers.rs around line 136, address this finding:
Annotated positional parameters with defaults cannot be parsed, even though the function parser explicitly supports positional defaults. In `parse_function_stmt`, after consuming `:`, `parse_inline_type_until_param_delim()` is used, but that collector does not stop at a top-level `Token::Assign`; it consumes `= default_expr` along with the type, so `fn f(x: Int = 1) { ... }` is rendered as something like `Int = 1` and rejected as an invalid type. This is disproven only if the intended grammar forbids typed positional defaults (contradicting the parser's default-handling branch and the documented/defaulted-parameter obligation) or if another earlier parser path intercepts this spelling.

In core/src/typ/type_checker/expressions/stdlib.rs, address this finding:
Declared stdlib parameter types are silently ignored for any optional parameter when that parameter is supplied positionally. Both direct positional checking and named checking call `constrain_stdlib_argument`, whose first branch returns immediately for `param.optional`, so a signature such as an optional `Int` accepts a supplied `String` instead of enforcing the declared type. The same behavior is present in the positional loop's `if param.optional ... { continue; }`. This is disproven only if `optional` is deliberately documented to mean 'untyped whenever present' rather than merely 'may be omitted'; the signature comments describe it as omission eligibility, and the richer-signature obligation requires supplied arguments to honor their declared types.

In core/src/stmt/defer.rs around line 144, address this finding:
A `defer` inside a trait default method is not rewritten before the trait default is copied into implementations. `desugar_defers` only descends through functions, impl methods, blocks, conditionals, loops, and try expressions; it has no `Stmt::Trait` arm to visit `Trait.default_methods`. The later `apply_trait_defaults` clones those default method ASTs into `Impl.methods`, leaving `Stmt::Defer` in the generated method for the resolver/type checker/compiler, contrary to the invariant that defer is erased after macros and before all downstream consumers. This is disproven only if trait default method bodies are syntactically forbidden from containing defer, but the parser's ordinary function-body grammar and the documented defer obligation do not establish such a restriction.

In core/src/val/runtime_model/equality.rs around line 163, address this finding:
Structural equality of objects is not depth-bounded, so cyclic or sufficiently deep object graphs can recurse until Rust stack overflow instead of returning the documented comparison error.

In core/src/vm/verify.rs around line 48, address this finding:
The artifact verifier accepts functions whose `param_count` exceeds `register_count`, but flattened call setup copies every argument to `new_base + i` after allocating only `register_count` slots. A crafted artifact (or a module assembled by a host) with a 1-register, 2-parameter callee can therefore overwrite the next frame/stack region or panic when called, rather than being rejected at load time.

In lkrt/src/stack_guard.c, address this finding:
The stack-exhaustion guard is not safe for the OS-thread concurrency model: it installs one process-wide SIGSEGV/SIGBUS handler but computes a single address interval from the main thread's stack and installs the alternate stack only on the calling thread. A spawned task that exhausts its own stack therefore faults outside `lk_stack_low..lk_stack_high`, the handler restores the default disposition and returns, and the process dies with an undiagnosed SIGSEGV/SIGBUS (and the child has no alternate signal stack, so the handler may itself be unable to run). This violates the promised diagnosed stack-guard outcome for concurrent native tasks. The claim would be false only if spawned tasks were guaranteed never to recurse/overflow or every spawned thread independently called `lk_install_stack_guard` with per-thread stack bounds/alternate storage.

In core/src/typ/type_checker/expressions/stdlib.rs around line 217, address this finding:
Generated stdlib signatures do not enforce the declared type of optional parameters, so invalid calls pass type checking and only fail in the native implementation. In `constrain_stdlib_argument` the `param.optional` early return skips assignability entirely, even though `param.ty` is the actual declared type (for example `pad?: String`, `end?: Int`, or `min?: Int`). Thus a call such as `use string; string.pad_left("x", 3, 4)` or `use math; math.clamp(1, min: "bad")` is accepted by the checker but reaches the runtime wrapper and raises a misleading native argument error. This was introduced by the generated-signature path (the old fallback did not claim these declarations); it would be disproven only if optional arguments were intentionally unconstrained by the public API, contrary to the generated signature/type documentation and runtime checks.

In core/src/typ/stdlib_sig.rs around line 116, address this finding:
The generated type-signature registry silently overwrites conflicting declarations instead of rejecting them. `register_stdlib_signatures` calls `HashMap::insert` for every path without comparing an existing entry, so registering two modules/metadata tables that both declare `math.abs` differently leaves whichever table registered last as the checker truth, while the parallel `StdlibMetadataRegistry` reports a conflict. This can make registration order change accepted argument types/return types and violates the obligation that metadata conflicts be reported consistently. The claim would be false only if this registry were guaranteed never to receive conflicting public inputs, but the function is public and the metadata layer explicitly treats duplicate paths as a conflict.

In bare-metal-x86/program.lk around line 2811, address this finding:
`build_user_space` leaks every page it allocated when any later page allocation fails (and callers also cannot reclaim a successfully built address space if `spawn_user_task` fails). The four allocations are performed into locals and the failure branch immediately returns 0 without `pages_release_run`/`release_page`. On a near-exhausted page arena, e.g. the first three allocations succeed and the fourth returns 0, those three pages remain counted as used and are unavailable forever; repeated user-space creation or other device/task allocations then fail despite the failed space never being live. This violates the page/user-address-space lifetime invariant.

In ecosystem/tree-sitter-lk/grammar.js around line 476, address this finding:
The editor grammars do not recognize the newly supported `defer` statement, so Tree-sitter/VS Code syntax support is not compatible with the language parser.

In cli/src/native_compile.rs around line 451, address this finding:
The native execution cache can reuse a binary built with different AOT orchestration settings, including pure-native versus hybrid mode.

In cli/src/native_compile.rs around line 26, address this finding:
The AOT driver builds `lk-api-cabi` through Cargo but then consumes a hard-coded `workspace/target/release/liblk_api_cabi.a`; when a caller sets Cargo's supported `CARGO_TARGET_DIR` (or uses a configured target directory), Cargo writes the archive elsewhere and the driver still passes the nonexistent hard-coded path to the linker, so hybrid/Tier-0 compilation fails despite the producer build succeeding.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `form-data@4.0.5` is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the `mailto:` validator scan-loop on attacker text); upgrade to at least 5.0.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `tmp@0.2.5` is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `markdown-it@14.1.1` is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `qs@6.15.1` is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.

In Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.

In Cargo.lock, address this finding:
Dependency `memmap2@0.2.3` is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate `memmap2`); upgrade to at least 0.9.11.

In bare-metal/Cargo.lock, address this finding:
Dependency `bare-metal@0.2.5` is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.

In ecosystem/zed-ext/Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.
📜 Review details

Coverage

  • scopes: 5/7 complete

let Some(right_value) = right.fields.get(name) else {
return Ok(false);
};
if !self.values(left_value, right_value, depth + 1)? {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Resource Lifetime | 🟠 High

Structural equality of objects is not depth-bounded, so cyclic or sufficiently deep object graphs can recurse until Rust stack overflow instead of returning the documented comparison error.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ The exact host/runtime API used to build a cyclic object may vary, but arbitrarily deep object chains are representable and sufficient to trigger unbounded recursion.
🤖 Prompt for AI agents
In core/src/val/runtime_model/equality.rs, address this finding:
Structural equality of objects is not depth-bounded, so cyclic or sufficiently deep object graphs can recurse until Rust stack overflow instead of returning the documented comparison error.

To have the bot fix this, comment @winnowl fix.

) -> Result<()> {
// An optional parameter is left unconstrained: the declaration says
// what it accepts when present, not that the argument *is* one.
if param.optional || param.ty == Type::Any {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟡 Medium

Generated stdlib signatures do not enforce the declared type of optional parameters, so invalid calls pass type checking and only fail in the native implementation. In constrain_stdlib_argument the param.optional early return skips assignability entirely, even though param.ty is the actual declared type (for example pad?: String, end?: Int, or min?: Int). Thus a call such as use string; string.pad_left("x", 3, 4) or use math; math.clamp(1, min: "bad") is accepted by the checker but reaches the runtime wrapper and raises a misleading native argument error. This was introduced by the generated-signature path (the old fallback did not claim these declarations); it would be disproven only if optional arguments were intentionally unconstrained by the public API, contrary to the generated signature/type documentation and runtime checks.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
  • ⚠️ I did not execute the checker/runtime, so the exact wording of the native argument error is unverified.
🤖 Prompt for AI agents
In core/src/typ/type_checker/expressions/stdlib.rs, address this finding:
Generated stdlib signatures do not enforce the declared type of optional parameters, so invalid calls pass type checking and only fail in the native implementation. In `constrain_stdlib_argument` the `param.optional` early return skips assignability entirely, even though `param.ty` is the actual declared type (for example `pad?: String`, `end?: Int`, or `min?: Int`). Thus a call such as `use string; string.pad_left("x", 3, 4)` or `use math; math.clamp(1, min: "bad")` is accepted by the checker but reaches the runtime wrapper and raises a misleading native argument error. This was introduced by the generated-signature path (the old fallback did not claim these declarations); it would be disproven only if optional arguments were intentionally unconstrained by the public API, contrary to the generated signature/type documentation and runtime checks.

To have the bot fix this, comment @winnowl fix.

Comment thread cli/src/native_compile.rs
// that an ordinary `cargo build`/`cargo test` stops emitting 172MB of it
// for a linker path it never takes. See that crate's docs. The `ffi`
// feature now rides along in its manifest rather than on this command line.
let staticlib = workspace.join("target/release/liblk_api_cabi.a");

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Correctness | 🟡 Medium

The AOT driver builds lk-api-cabi through Cargo but then consumes a hard-coded workspace/target/release/liblk_api_cabi.a; when a caller sets Cargo's supported CARGO_TARGET_DIR (or uses a configured target directory), Cargo writes the archive elsewhere and the driver still passes the nonexistent hard-coded path to the linker, so hybrid/Tier-0 compilation fails despite the producer build succeeding.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In cli/src/native_compile.rs, address this finding:
The AOT driver builds `lk-api-cabi` through Cargo but then consumes a hard-coded `workspace/target/release/liblk_api_cabi.a`; when a caller sets Cargo's supported `CARGO_TARGET_DIR` (or uses a configured target directory), Cargo writes the archive elsewhere and the driver still passes the nonexistent hard-coded path to the linker, so hybrid/Tier-0 compilation fails despite the producer build succeeding.

To have the bot fix this, comment @winnowl fix.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔎 Confirmed findings (1)
  • medium Partial user address-space construction leaks every page allocated before the first allocation failure. (inline)

⛔ Unresolved from previous review (30) — not approved until fixed

  • The Tier-1 VM bridge uses one process-global argument buffer, so concurrent native calls can overwrite each other's marshaled arguments.
  • core/src/stmt/defer.rs: Defer statements in trait default method bodies are not desugared, so a default copied into an implementation does not release on returns/fall-through. For example, trait T { fn f() { defer cleanup(); return 1; } } impl T for S {} leaves the Stmt::Defer inside the trait's function because descend has no Stmt::Trait arm; the later apply_trait_defaults clones that still-unrewritten function into the impl.
  • aot/lower/src/function.rs: return; inside an outlined try body does not propagate the enclosing-function return channel. The region scanner marks Opcode::Return0 as body_returns and the caller allocates/passes flag and value cells, but the body lowering only parks a return when matching Some(Exit::Ret(Some(reg))); Exit::Ret(None) bypasses that code and leaves the flag at its seeded false value. On the normal body-completion edge the caller therefore treats return; as a raised/catch outcome (or, for an all-return body, reaches the wrong continuation) instead of returning nil from the enclosing function. A minimal failure is try { return; } catch e { print("caught") } in a non-entry function: the VM returns immediately, while native lowering enters the catch path. This would be disproved if Return0 were converted to an Exit::Ret(Some(...)) before this match or if another lowering path sets the return flag for Ret(None), neither of which is present in the inspected code.
  • The Tier-1 hybrid bridge uses one module-global lk_hybrid_argbuf for every CallVm site, but the native runtime also supports concurrent spawned tasks. If two native threads execute a bridged call concurrently, each overwrites the shared tags/payloads before the other bridge reads them, so the VM function can receive another thread's arguments (or inconsistent tag/value pairs). The comment's single-threaded assumption is not enforced by the language/runtime.
  • cli/src/native_compile.rs: Native executable cache entries are not invalidated when the linked lkrt/runtime or toolchain changes, allowing an executable with an old ABI or runtime implementation to be reused for unchanged source.
  • aot/codegen/src/clif.rs: Tier 1 hybrid calls are not safe when native code executes concurrently: all generated CallVm sites share one writable global argument buffer, so threads can overwrite each other's tagged arguments before the bridge reads them.
  • core/src/stmt/defer.rs: Trait default methods containing defer are never desugared, so a defer survives into type checking/compilation after the promised pre-consumer rewrite. desugar_defers descends into functions and impl methods but has no Stmt::Trait arm; expand_program_source then copies trait defaults into impls after desugaring. A program such as trait T { fn f() { defer release(); return 1; } } impl T for S {} therefore copies a raw Stmt::Defer into the generated method (and even a trait default used nowhere remains unprocessed), violating the invariant that downstream consumers never see Stmt::Defer. This is introduced by combining trait-default expansion with the existing defer traversal; it would be false only if trait default bodies are guaranteed by the parser/macros never to contain defer or are rewritten elsewhere before checking.
  • core/src/val/runtime_model/equality.rs: Structural equality of nested objects bypasses the depth guard and can recurse until Rust stack overflow.
  • aot/codegen/src/clif.rs: The Tier-1 hybrid argument buffer is a single mutable module-global, but native spawn/go execute compiled callbacks on OS threads. Two concurrent hybrid calls (or a callback racing the parent) overwrite tags/values between marshaling and lk_hybrid_call_*, causing the VM to receive another call's arguments.
  • The native dynamic-value work adds Set and Bytes carrier tags (and documents them as values that can cross a bridged return), but the hybrid VM bridge still only marshals Nil/Bool/Int/Float/Str/List/Map: any VM function returning a Set or Bytes reaches the Some(other) => hybrid_die(...) arm and terminates instead of returning the new LkDyn. The public header likewise advertises tags only through MAP, so a Tier-1 hybrid caller cannot consume these values despite the runtime/ABI supporting them.
  • bare-metal-x86/src/user.rs: The ring-3 syscall trampoline enters the compiled dispatcher with a misaligned stack: after the CPU's ring transition, the seven pushes leave RSP 16-byte aligned, but sub rsp, 264 changes it to RSP%16 == 8 immediately before call lk_syscall_dispatch (the ABI requires the caller's RSP to be 0 mod 16 before a call). A dispatcher or callee that emits an aligned SSE stack spill can fault or misbehave on every syscall, so the user boundary is not ABI-correct; reserving 256 bytes (or otherwise explicitly aligning) would preserve the required phase. This is introduced by the new syscall trampoline; it would be disproved only if the actual native LK calling convention explicitly requires the opposite stack phase and all possible callees avoid alignment-sensitive spills.
  • core/src/stmt/defer.rs: defer inside if let/while let is neither rejected nor rewritten, so a deferred statement can reach downstream passes and returns in those bodies do not release pending resources.
  • Native execution can reuse stale cached binaries after an imported module changes.
  • core/src/val/runtime_model/equality.rs: Structural equality does not enforce MAX_VALUE_DEPTH for object-field recursion, so deeply nested or cyclic object graphs can overflow the Rust stack instead of returning the documented comparison error.
  • aot/lower/src/function.rs: A try body that returns can exceed the trampoline's fixed arity even though lowering accepts it, causing the generated native program to trap instead of executing the VM semantics.
  • aot/codegen/src/clif.rs: The hybrid bridge's shared argument buffer is not safe for concurrent native execution: two native threads can overwrite one another's tagged slots and payloads between marshaling and lk_hybrid_call_*, so the VM receives a mixed argument vector.
  • api/src/lib.rs: The hybrid return bridge advertises the same LkDyn carrier and DYN_LIST/DYN_MAP tags as lkrt, but its container-return path is only wired for the injected list/map constructors and cannot marshal typed string lists (it unconditionally dies for TypedList::String). Consequently a VM function returning a valid string list can be selected by the AOT hybrid lowering and then terminate the native process at the bridge, while the VM and ordinary native stdlib paths return it normally.
  • aot/codegen/src/clif.rs: Hybrid bridge calls are not isolated when native tasks run concurrently: every generated function shares one mutable lk_hybrid_argbuf, so two spawned threads can overwrite each other's tagged arguments between the stores and lk_hybrid_call_*. For example, two spawn0/spawn1 callbacks containing CallVm can race: thread A writes argument 1, thread B writes argument 2, then A invokes the VM with B's value (and vice versa), producing nondeterministic results despite the runtime explicitly creating OS threads. The comment's single-thread assumption is contradicted by the exported spawn implementation, and the bridge's VM mutex only serializes after arguments have already been read. This would be false only if the native lowering forbade any CallVm execution from spawned functions or otherwise serialized all bridge callers, but the runtime exposes spawn for compiled callbacks and no such synchronization is present.
  • core/src/val/runtime_model/equality.rs: Structural equality does not enforce the configured depth limit for nested structs, so cyclic or sufficiently deep object graphs can recurse until a Rust stack overflow instead of returning the documented comparison error.
  • api/src/lib.rs: Vm::eval_value can overflow the host stack instead of returning an error for cyclic VM containers.
  • The native executable cache key omits imported source contents even though this change now bundles file imports during native compilation, so editing an imported .lk file can silently execute stale cached code.
  • core/src/val/runtime_model/equality.rs: Canonical structural equality does not enforce its depth limit for object fields. In Comparison::objects, each field is compared with self.values(left_value, right_value, depth + 1) instead of self.nested(...); therefore a chain of deeply nested structs (or a cyclic object graph if one is constructed through runtime mutation) bypasses the MAX_VALUE_DEPTH check and recurses through Rust stack frames until stack overflow, violating the stated no-process-abort comparison limit. The claim is false only if object graphs are proven impossible to nest beyond the limit/cycle in every supported runtime path, or if object equality is intentionally exempt from the depth contract.
  • lkrt/src/chan.rs: An uncaught runtime raise in a spawned task terminates the whole native process instead of becoming a failed task that task.await can catch/report.
  • lk bundle does not produce a self-contained executable for programs with file or package imports, despite the command and output path documenting a self-contained bundle.
  • The native executable cache key omits imported source files even though the AOT pipeline now bundles file imports before code generation. Editing an imported module leaves the entry source hash unchanged, so try_execute_cached_native can run a stale executable containing the old imported code instead of rebuilding.
  • core/src/stmt/defer.rs: if let is omitted from both defer traversal and release insertion. With a top-level defer followed by if let p = value { return ... }, with_releases matches no Stmt::IfLet arm and leaves the nested return unreleased; with a defer written inside the if let body, descend also has no IfLet arm, so reject_stray never reports the forbidden placement and the raw defer reaches downstream consumers. This is a concrete missing branch variant in the AST rewrite and would be false only if IfLet were impossible in function bodies, which the parser and compiler explicitly support.
  • core/src/val/runtime_model/equality.rs: Structural equality of objects bypasses the depth guard and can recurse until Rust stack overflow on deeply nested/cyclic structs.
  • stdlib/common/src/language.rs: try_call cannot safely round-trip heap-valued error(...) from a RuntimeCallable: it clears the caller's pending root and returns the callee's raw heap handle into the caller heap.
  • core/src/val/runtime_model/equality.rs: Structural equality of objects is not depth-bounded, so cyclic or sufficiently deep object graphs can recurse until Rust stack overflow instead of returning the documented comparison error.
  • The stack-exhaustion guard is not safe for the OS-thread concurrency model: it installs one process-wide SIGSEGV/SIGBUS handler but computes a single address interval from the main thread's stack and installs the alternate stack only on the calling thread. A spawned task that exhausts its own stack therefore faults outside lk_stack_low..lk_stack_high, the handler restores the default disposition and returns, and the process dies with an undiagnosed SIGSEGV/SIGBUS (and the child has no alternate signal stack, so the handler may itself be unable to run). This violates the promised diagnosed stack-guard outcome for concurrent native tasks. The claim would be false only if spawned tasks were guaranteed never to recurse/overflow or every spawned thread independently called lk_install_stack_guard with per-thread stack bounds/alternate storage.
🧹 Additional findings from this change (not shown inline) (16)
  • [high] A defer at program scope is silently accepted and rewritten instead of being rejected as outside a function.
  • [high] Trait default methods containing defer bypass the defer rewrite and leave a downstream Stmt::Defer.
  • [medium] Macro ty fragments do not accept the same type spellings as ordinary annotations: the fragment scanner stops at * and :, so pointer types and function types with named parameters are truncated before validation.
  • [medium] Declared stdlib optional parameters are not type-checked when supplied, so checker/runtime disagree on valid argument types. For example, a registered signature such as bytes.slice(bytes, start, end?: Int) accepts bytes.slice(b, 0, "bad") because both the positional and named paths immediately continue/return when param.optional is true; the runtime wrapper still receives the value and validates the declared Int, causing a runtime error for a program the checker accepted. The claim would be false only if the stdlib wrapper intentionally accepts arbitrary values for every optional parameter (contradicting the exported type declaration and runtime validation).
  • [medium] The analysis-only slot resolver does not declare named parameters for nested Stmt::Function or impl-method layouts, even though the type checker and runtime treat them as parameters. In resolve_stmt, both function branches destructure only name, params, body and allocate the positional parameters, so a reference to a named parameter (or its default expression) is unresolved and omitted from decls/uses; this breaks the promised slot/capture/shadowing model for a nested function layout and makes tools using SlotResolution disagree with actual frames. The claim would be false only if named parameters are guaranteed never to appear in these AST function variants or the resolver is explicitly specified to omit them, neither of which holds since Stmt::Function carries named_params and resolve_function_slots already accepts them.
  • [medium] Bare-metal and web host registration never publishes their builtin global arities to the checker, so the advertised global signatures are not available when those hosts are used standalone.
  • [medium] lk compile does not enforce the same strict type-checking policy as lk check/VM execution: compile_instr_artifact_with_dependencies constructs TypeChecker::new() (non-strict) before compiling, while run_type_check and VmContext use TypeChecker::new_strict(). A program whose inference leaves an implicit Any can therefore pass the compile preflight and be emitted as native/bundled, whereas lk check rejects it and direct execution rejects it, violating consistent CLI compile semantics. This would be disproven if the compiler or every compile entry point independently applies strict checking before emitting the artifact; compile_program_module_with_ctx itself only compiles/imports and does not type-check.
  • [high] The native cache key omits the selected runtime/toolchain, so a cache hit can execute an artifact linked against a different custom runtime or linker/compiler. For example, compile foo.lk with LKRT_STATICLIB=/tmp/rt-a.a (or LK_CLANG=clang-a), then change that variable to rt-b.a and run with native caching enabled: cached_native_executable_path hashes only source/version, LK_NATIVE_SANITIZE, and the CLI executable metadata, then returns the existing executable without rebuilding. This can mix incompatible ABI/runtime state despite the cache/concurrency obligation. The claim would be false if those variables are guaranteed immutable for the lifetime of the cache directory or are incorporated into an external cache namespace before this lookup.
  • [medium] Concurrent native-cache builds in the same process can delete each other's temporary executable. native_cache_tmp_path is deterministic (&lt;cache-exe&gt;.tmp-&lt;process id&gt;), so two concurrent callers for the same source compile to the same path; one can still be writing while the other sees rename fail, calls remove_file(&amp;tmp), and thereby removes the first caller's in-progress output. The first caller then cannot atomically install a complete binary, causing spurious cache misses/failures (and the retry logic only handles another process that finished, not a same-process writer). This would be disproven if the CLI guarantees try_execute_cached_native is never called concurrently within a process; the code itself provides no such synchronization or unique per-thread temporary name.
  • [medium] Kernel heap reservation leaks pages whenever the 16-page contiguous reservation is incomplete. The startup loop allocates pages one by one, but if any allocation is 0 or non-contiguous it only sets up an empty heap and never releases the already allocated pages (and it continues allocating after failure). On a low-memory or fragmented range this permanently reduces the page allocator, so later task/user/NIC allocations can fail even though the heap was never usable.
  • [medium] Task deadlines are not wrap-safe even though the shared tick counter is explicitly treated as a wrapping 32-bit value elsewhere. task_sleep stores shared_read(SHARED_TICKS) + ticks, while task_wake_due tests deadline &gt; now; when a sleep crosses the 32-bit tick wrap, the wrapped deadline is already <= the pre-wrap now, so the task is woken immediately rather than after the requested interval (and a deadline set just after wrap can be treated as still far in the future).
  • [medium] The generated-parser freshness test does not actually verify the committed parser artifact that tree-sitter uses.
  • [medium] The ASan lkrt differential job does not use the matching nightly-built lk-api archive that its own build script produces.
  • [medium] VSIX packaging is not reproducible because the extension has no committed npm lockfile and the Make target runs npm install against caret-ranged dependencies.
  • [high] The Tier-1 hybrid lowering unconditionally emits an external fflush call before every CallVm, but the supported bare-metal runtime is built no_std and deliberately has no fflush symbol. A bare-metal target containing a hybrid bridge therefore produces an object whose link contract cannot be satisfied by the bare-metal lkrt archive, despite the codegen target-selection path accepting bare-metal triples.
  • [high] The hybrid bridge's argument buffer is a single mutable module-global (lk_hybrid_argbuf) and the bridge runtime is protected only by a mutex after arguments have been marshaled. If native code can execute bridge calls concurrently (the repository also adds native task/channel support), concurrent callers overwrite one another's tags and payloads before the serialized bridge call reads them, causing cross-call argument corruption.
♻️ Previously reported (still present) (27)
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@1.1.15 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@2.1.1 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.
  • [high] Dependency brace-expansion@5.0.6 is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.
  • [high] Dependency fast-uri@3.1.2 is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.
  • [high] Dependency form-data@4.0.5 is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.
  • [high] Dependency js-yaml@4.1.1 is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.
  • [high] Dependency linkify-it@5.0.0 is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the mailto: validator scan-loop on attacker text); upgrade to at least 5.0.2.
  • [high] Dependency tmp@0.2.5 is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.
  • [high] Dependency undici@7.25.0 is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.
  • [medium] Dependency js-yaml@4.1.1 is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.
  • [medium] Dependency markdown-it@14.1.1 is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.
  • [medium] Dependency qs@6.15.1 is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.
  • [medium] Dependency undici@7.25.0 is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.
  • [low] Dependency undici@7.25.0 is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
  • [info] Dependency memmap2@0.2.3 is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate memmap2); upgrade to at least 0.9.11.
  • [info] Dependency bare-metal@0.2.5 is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.
  • [info] Dependency anyhow@1.0.102 is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in Error::downcast_mut()); upgrade to at least 1.0.103.
🗑️ Suppressed and duplicate diagnostics (1)
  • Partial user address-space construction leaks every page allocated before the first allocation failure. If, for example, pml4 and pdpt succeed but directory or table returns 0, the function returns without releasing the successful allocations; repeated user-task creation or low-memory operation permanently consumes those pages.
🤖 Prompt for AI agents — all findings (44)
In core/src/stmt/defer.rs, address this finding:
A `defer` at program scope is silently accepted and rewritten instead of being rejected as outside a function.

In core/src/syntax.rs around line 97, address this finding:
Trait default methods containing `defer` bypass the defer rewrite and leave a downstream `Stmt::Defer`.

In core/src/macro_system/expansion.rs, address this finding:
Macro `ty` fragments do not accept the same type spellings as ordinary annotations: the fragment scanner stops at `*` and `:`, so pointer types and function types with named parameters are truncated before validation.

In core/src/typ/type_checker/expressions/stdlib.rs, address this finding:
Declared stdlib optional parameters are not type-checked when supplied, so checker/runtime disagree on valid argument types. For example, a registered signature such as `bytes.slice(bytes, start, end?: Int)` accepts `bytes.slice(b, 0, "bad")` because both the positional and named paths immediately `continue`/return when `param.optional` is true; the runtime wrapper still receives the value and validates the declared `Int`, causing a runtime error for a program the checker accepted. The claim would be false only if the stdlib wrapper intentionally accepts arbitrary values for every optional parameter (contradicting the exported type declaration and runtime validation).

In core/src/resolve/slots.rs around line 332, address this finding:
The analysis-only slot resolver does not declare named parameters for nested `Stmt::Function` or impl-method layouts, even though the type checker and runtime treat them as parameters. In `resolve_stmt`, both function branches destructure only `name, params, body` and allocate the positional parameters, so a reference to a named parameter (or its default expression) is unresolved and omitted from `decls`/`uses`; this breaks the promised slot/capture/shadowing model for a nested function layout and makes tools using `SlotResolution` disagree with actual frames. The claim would be false only if named parameters are guaranteed never to appear in these AST function variants or the resolver is explicitly specified to omit them, neither of which holds since `Stmt::Function` carries `named_params` and `resolve_function_slots` already accepts them.

In stdlib/bare/src/lib.rs around line 87, address this finding:
Bare-metal and web host registration never publishes their builtin global arities to the checker, so the advertised global signatures are not available when those hosts are used standalone.

In cli/src/native_compile.rs around line 93, address this finding:
`lk compile` does not enforce the same strict type-checking policy as `lk check`/VM execution: `compile_instr_artifact_with_dependencies` constructs `TypeChecker::new()` (non-strict) before compiling, while `run_type_check` and `VmContext` use `TypeChecker::new_strict()`. A program whose inference leaves an implicit `Any` can therefore pass the compile preflight and be emitted as native/bundled, whereas `lk check` rejects it and direct execution rejects it, violating consistent CLI compile semantics. This would be disproven if the compiler or every compile entry point independently applies strict checking before emitting the artifact; `compile_program_module_with_ctx` itself only compiles/imports and does not type-check.

In cli/src/native_compile.rs, address this finding:
The native cache key omits the selected runtime/toolchain, so a cache hit can execute an artifact linked against a different custom runtime or linker/compiler. For example, compile `foo.lk` with `LKRT_STATICLIB=/tmp/rt-a.a` (or `LK_CLANG=clang-a`), then change that variable to `rt-b.a` and run with native caching enabled: `cached_native_executable_path` hashes only source/version, `LK_NATIVE_SANITIZE`, and the CLI executable metadata, then returns the existing executable without rebuilding. This can mix incompatible ABI/runtime state despite the cache/concurrency obligation. The claim would be false if those variables are guaranteed immutable for the lifetime of the cache directory or are incorporated into an external cache namespace before this lookup.

In cli/src/native_compile.rs, address this finding:
Concurrent native-cache builds in the same process can delete each other's temporary executable. `native_cache_tmp_path` is deterministic (`<cache-exe>.tmp-<process id>`), so two concurrent callers for the same source compile to the same path; one can still be writing while the other sees `rename` fail, calls `remove_file(&tmp)`, and thereby removes the first caller's in-progress output. The first caller then cannot atomically install a complete binary, causing spurious cache misses/failures (and the retry logic only handles another process that finished, not a same-process writer). This would be disproven if the CLI guarantees `try_execute_cached_native` is never called concurrently within a process; the code itself provides no such synchronization or unique per-thread temporary name.

In bare-metal-x86/program.lk around line 2811, address this finding:
Partial user address-space construction leaks every page allocated before the first allocation failure.

In bare-metal-x86/program.lk, address this finding:
Kernel heap reservation leaks pages whenever the 16-page contiguous reservation is incomplete. The startup loop allocates pages one by one, but if any allocation is 0 or non-contiguous it only sets up an empty heap and never releases the already allocated pages (and it continues allocating after failure). On a low-memory or fragmented range this permanently reduces the page allocator, so later task/user/NIC allocations can fail even though the heap was never usable.

In bare-metal-x86/drivers/tasks.lk around line 217, address this finding:
Task deadlines are not wrap-safe even though the shared tick counter is explicitly treated as a wrapping 32-bit value elsewhere. `task_sleep` stores `shared_read(SHARED_TICKS) + ticks`, while `task_wake_due` tests `deadline > now`; when a sleep crosses the 32-bit tick wrap, the wrapped deadline is already <= the pre-wrap `now`, so the task is woken immediately rather than after the requested interval (and a deadline set just after wrap can be treated as still far in the future).

In lsp/src/editor_grammar_test.rs around line 142, address this finding:
The generated-parser freshness test does not actually verify the committed parser artifact that tree-sitter uses.

In .github/workflows/correctness.yml around line 97, address this finding:
The ASan lkrt differential job does not use the matching nightly-built lk-api archive that its own build script produces.

In Makefile around line 22, address this finding:
VSIX packaging is not reproducible because the extension has no committed npm lockfile and the Make target runs `npm install` against caret-ranged dependencies.

In aot/codegen/src/clif.rs around line 1369, address this finding:
The Tier-1 hybrid lowering unconditionally emits an external `fflush` call before every `CallVm`, but the supported bare-metal runtime is built `no_std` and deliberately has no `fflush` symbol. A bare-metal target containing a hybrid bridge therefore produces an object whose link contract cannot be satisfied by the bare-metal `lkrt` archive, despite the codegen target-selection path accepting bare-metal triples.

In aot/codegen/src/clif.rs, address this finding:
The hybrid bridge's argument buffer is a single mutable module-global (`lk_hybrid_argbuf`) and the bridge runtime is protected only by a mutex after arguments have been marshaled. If native code can execute bridge calls concurrently (the repository also adds native task/channel support), concurrent callers overwrite one another's tags and payloads before the serialized bridge call reads them, causing cross-call argument corruption.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@1.1.15` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@2.1.1` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-3jxr-9vmj-r5cp (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups); upgrade to at least 5.0.7.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `brace-expansion@5.0.6` is affected by high advisory GHSA-mh99-v99m-4gvg (brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash); upgrade to at least 5.0.8.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-4c8g-83qw-93j6 (fast-uri vulnerable to host confusion via failed IDN canonicalization); upgrade to at least 4.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `fast-uri@3.1.2` is affected by high advisory GHSA-v2hh-gcrm-f6hx (fast-uri vulnerable to host confusion via literal backslash authority delimiter); upgrade to at least 2.4.3.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `form-data@4.0.5` is affected by high advisory GHSA-hmw2-7cc7-3qxx (form-data: CRLF injection in form-data via unescaped multipart field names and filenames); upgrade to at least 2.5.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by high advisory GHSA-52cp-r559-cp3m (js-yaml: YAML merge-key chains can force quadratic CPU consumption); upgrade to at least 3.15.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-22p9-wv53-3rq4 (LinkifyIt#match scan loop has quadratic algorithmic complexity); upgrade to at least 5.0.1.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `linkify-it@5.0.0` is affected by high advisory GHSA-v245-v573-v5vm (linkify-it: Quadratic-complexity DoS via the `mailto:` validator scan-loop on attacker text); upgrade to at least 5.0.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `tmp@0.2.5` is affected by high advisory GHSA-ph9p-34f9-6g65 (tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape); upgrade to at least 0.2.6.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-hm92-r4w5-c3mj (undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vmh5-mc38-953g (undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by high advisory GHSA-vxpw-j846-p89q (undici WebSocket client vulnerable to denial of service via fragment count bypass); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `js-yaml@4.1.1` is affected by medium advisory GHSA-h67p-54hq-rp68 (JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases); upgrade to at least 4.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `markdown-it@14.1.1` is affected by medium advisory GHSA-6v5v-wf23-fmfq (markdown-it: Quadratic complexity DoS in smartquotes rule via replaceAt string operations); upgrade to at least 14.2.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `qs@6.15.1` is affected by medium advisory GHSA-q8mj-m7cp-5q26 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/undefined entries in comma-format arrays when encodeValuesOnly is set); upgrade to at least 6.15.2.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-p88m-4jfj-68fv (undici vulnerable to HTTP header injection via Set-Cookie percent-decoding); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by medium advisory GHSA-pr7r-676h-xcf6 (undici vulnerable to cross-user information disclosure via shared cache whitespace bypass); upgrade to at least 7.28.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-35p6-xmwp-9g52 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); upgrade to at least 6.27.0.

In ecosystem/vsc-ext/lsp/package-lock.json, address this finding:
Dependency `undici@7.25.0` is affected by low advisory GHSA-g8m3-5g58-fq7m (undici vulnerable to Set-Cookie SameSite attribute downgrade via permissive substring matching); upgrade to at least 6.27.0.

In Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.

In Cargo.lock, address this finding:
Dependency `memmap2@0.2.3` is affected by info advisory RUSTSEC-2026-0186 (Unchecked pointer offset in crate `memmap2`); upgrade to at least 0.9.11.

In bare-metal/Cargo.lock, address this finding:
Dependency `bare-metal@0.2.5` is affected by info advisory RUSTSEC-2026-0110 (bare-metal is deprecated); no fixed version is available yet.

In ecosystem/zed-ext/Cargo.lock, address this finding:
Dependency `anyhow@1.0.102` is affected by info advisory RUSTSEC-2026-0190 (Unsoundness in `Error::downcast_mut()`); upgrade to at least 1.0.103.
📜 Review details

Coverage

  • scopes: 6/8 complete

Comment thread bare-metal-x86/program.lk
let pdpt = page_alloc(SHARED_PAGES);
let directory = page_alloc(SHARED_PAGES);
let table = page_alloc(SHARED_PAGES);
if (pml4 == 0 || pdpt == 0 || directory == 0 || table == 0) {

ghost Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Resource Lifetime | 🟡 Medium

Partial user address-space construction leaks every page allocated before the first allocation failure.

🧩 Analysis
  • Change relation: introduced
  • Confirmation: independently-verified
  • Reachable: ✅
🤖 Prompt for AI agents
In bare-metal-x86/program.lk, address this finding:
Partial user address-space construction leaks every page allocated before the first allocation failure.

To have the bot fix this, comment @winnowl fix.

@lollipopkit

ghost commented Jul 31, 2026

Copy link
Copy Markdown
Owner Author

@winnowl ignore

@winnowl

ghost commented Jul 31, 2026

Copy link
Copy Markdown

Ignoring this PR: new commits will no longer trigger a review. Run one on demand with @winnowl review (which does not lift it), or @winnowl unignore / @winnowl resume to restore. To ignore a single finding instead, reply @winnowl ignore under its inline comment.

lollipopkit🏳️‍⚧️ added 3 commits August 5, 2026 22:21
    let l: List<Int> = [1];
    l[0] = "a";
    let n: Int = l[0];
    println(n + 1);          // 打印 a1

而方法拼写是拒的:`l.set(0, "a")` 报 "Argument 2 has the wrong type"。同一个操作的
两种拼写,只有一种被检查。四个目标都成立:列表元素、map 值、map 键、结构体字段。

根在解析期的降解 —— 三种写法各变成一个不同的隐藏调用,谁也没有校验值:
`l[0] = v`(整数字面量下标)→ `list.set(l, 0, v)`;`l[i] = v` / `m[k] = v` →
`__lk_set_index`;`s.f = v` → `__lk_set_field`。

现在三处都调同一个 `check_container_store`:按容器类型取出声明的元素/值类型(结构体
取字段声明的类型)与被存的值比;map 另比键的声明类型 —— `Map<String,Int>` 上的
`m[7] = 2` 此前收下,map 里就出现了 Int 键。

两条边界照旧:元素类型还是类型变量时教它而不是拒它(`let l = []; l.push(1);
l[0] = "a";` 仍然可以,与实参那条同规矩);`Any` 两侧放行。

发现路径:探 `let l = [1]; l.push("a")` 与 `let l = []` 的不一致时,顺手比 map 的同一格
——发现 map 那格"过"不是因为拓宽,是压根没检查。
上一条(21bfad5)覆盖了 List / Map / 结构体三种容器,漏了第四种:异质列表字面量推成
`Tuple`,而 `check_container_store` 对它直接放行。

    let l = [1, "a"];
    l[0] = 2.5;
    let n: Int = l[0];
    println(n + 1);      // 打印 3.5

`Tuple` 的每个位置类型不同,所以按位置校验:下标是整数字面量就比那一位;下标不是
字面量时位置未知,值必须适配每一位 —— 读 `l[0]` 是按第 0 位定的类型,动态写落在那里
不能把它推翻。

这条修完,"元素类型什么时候是承诺"那张表一致了:空字面量教、非空字面量承诺、异质
字面量按位置、注解为准。此前只有 `let m = {"k": 1}` 那格"收",而它收的原因不是拓宽,
是压根没检查。
lollipopkit🏳️‍⚧️ added 29 commits August 21, 2026 14:28
… variant

`Unsupported` is the list of things AOT cannot lower, and `reason()` prints
each one to the user. Two variants were never constructed:

- `NonBoolCondition`, documented "int-truthiness not yet lowered". A
  condition in LK is truthiness, not `Bool` (docs/semantics.md) — `if 0`,
  `if ""`, `if []`, `if nil` and `n ? a : b` all lower natively today and
  agree with the VM. The claim was false in both directions: the variant
  could not fire, and the feature it named works.
- `NoReturn`, "the entry function never returns".

rustc does not catch this: a `pub` enum's variants count as reachable, so a
variant left behind when its lowering path is removed goes on advertising a
limitation nobody can hit. The enum's doc now says so.

Also here, both found while reading the same file: the doc comment for
`LiteralElemTypeContradicted` was attached to `PhiProvenance` (which has its
own, so the two ran together into one paragraph describing two different
things), and `InvalidMir` carried two comments about itself.

`truthiness` joins the construct coverage table (66/66) — the constructs it
covers had no gate, which is why a variant could claim they were unsupported
without anything contradicting it.
`every_language_construct_lowers_natively` runs each construct on the VM and
on its native build and collects the mismatches into `diverged` — and then
asserted `refused` and `stale` and returned. Nothing ever read `diverged`.
A construct that lowered natively and printed a *different answer* passed.

The comment above the collection says what it was for: "Lowering is half the
question; the other half is whether it lowers to the same answer." Only the
first half was wired to a result. Rust does not warn — pushing to the vec is
a use of it.

Asserted now, and all 66 constructs agree, so this fixes the gate rather than
any miscompile it was hiding.

Also adds the third backend to the same table: source → `.lkm` → run, against
running from source. `ModuleArtifact` encode/decode is a second implementation
of the module, and its oracle (`vm_bytecode_differential_test`) walks
`examples/` — measuring the programs the repository happens to contain, which
is the exact gap this table was written for. 66/66 round-trip identically.
`NativeEntry.name` was a `String`, and it is read by nothing but `bail!` —
it names the native in an arity or window error. Calling a native *through a
value* builds an entry per call to carry the arity and function pointer to
the helper that runs it, so `let f = typeof; f(x)` in a loop allocated and
freed the literal `"<runtime-native>"` once per iteration for a message it
never printed. The field is now `Cow<'static, str>`.

Measured, interleaved, dist profile, 10M calls through a bound native:

    before  0.62 0.62 0.62 0.63
    after   0.52 0.53 0.53 0.52

Every example in the repository reaches this site; the count is 1879 across
`examples/{syntax,stdlib,general}` and 34 in `stream_demo.lk` alone, which is
the shape CLAUDE.md already records as "a stdlib call written as a bare
global *is* this path".

`stream`'s `call_runtime_callable_value` took `context: &str` and copied it
into the same field; all three callers pass a literal, so it takes
`&'static str` and borrows.

Second, smaller: the string-interpolation fast path builds its result in a
7-byte stack array specifically so a short result costs no allocation, and
reached the integer arm through `to_string()` — allocating a `String` to
decide whether to avoid allocating. It now formats into a stack buffer.
Worth about 1.5% on interpolation-heavy code (0.965s -> 0.950s over 40M),
which is small; the reason to take it is that the allocation contradicted
the path it was on.

All nine gates pass; the perf geometric mean reads 0.998x.
Macros are expanded during *parsing*, and the REPL parses each input as its
own source text. So `macro_rules! m { … }` was accepted in silence and `m!()`
on the next line answered "no macro named `m` is defined" — while `fn`,
`struct`, `impl` and `let` all persisted. Writing both on one line worked,
which is what made it look like a syntax problem rather than a lifetime one.

`use { vec } from macros;` was the same defect and worse: an import is
collected into the same registry, so the builtin macro module could not be
used from the REPL at all unless the import and the call shared a line.

`ParseOptions::carried_macro_definitions` carries the definitions an earlier
expansion collected into the next parse, and `MacroExpandResult` hands them
back. `MacroDefinitions` is opaque — what a definition *is* stays inside
`macro_system`. The REPL records them after the input *executes*, so a failed
input defines nothing, matching the rule its other state already follows.

A source's own definition beats a carried one, so re-entering a name replaces
it. Inserting carried definitions first would instead raise "already defined
in this macro scope" — a collision on the name the previous line had just
been told did not exist.

`input_declares` now re-parses with the session's options too; without them an
input using a session macro does not parse, and the "nothing from this input
was defined" note was skipped for exactly the inputs it explains.
Each module has its own heap, and a heap value is a handle into one of them.
A cross-module call copies the *returned* value between the two heaps — that
is what makes `fn make_adder(n) -> (Int) -> Int` work across files. The
failure arm beside it returned the error untouched, so `error([7, 8, 9])`
arrived in the caller as a handle into a heap the catch cannot read:

    use { raise_list } from "raiser";
    try { raise_list(); } catch e { println(e[0]); }
    Error: heap object 88 out of bounds

An internal invariant, printed at the user. The native build printed `7`, so
this was also a VM/AOT divergence — and the *interpreter* was the wrong one.

Int and short-string payloads were unaffected: they are stored inline in the
value and carry no handle, so the obvious probe passes. That is why it
survived a corpus that already covers "first-class heap error values survive
the catch" (error_model_edges.lk) — within one module.

A payload that cannot cross at all (a bare closure) now degrades to the
message the raise already rendered, rather than handing on a handle that
faults later.

Gated as an example pair rather than a unit test, which puts it in front of
the VM/native sweep, the AOT coverage gate and the VM/bytecode differential
at once: coverage 74/74, sweep 74 identical, bytecode corpus 73. Reverted
locally, `cross_module_raise.lk` fails with the error above.

Still broken and not fixed here: the same crossing at a *task* boundary. A
heap payload raised inside `spawn` comes back to the VM as an unrelated value
(`<native fn println(...)>`), and the native build refuses a map with "value
cannot cross a channel". Both are the raise path missing a copy the return
path does; they are a separate change.
Three defects on one path, all of them the raise route missing a copy the
return route does.

**The VM lost the payload's heap.** A task runs against a `HeapStore` of its
own, and its result travels as a `RuntimePayload` — the value plus the heap it
lives in — so the awaiting side can copy it out. A raise had no such carrier:
the error propagated with a bare handle and the task's heap was dropped the
moment the future returned. `error([1, 2, 3])` inside `spawn` was caught as
whatever object now sat at that index:

    let t = spawn(|| { error([1, 2, 3]); return 0; });
    try { task.await(t); } catch e { println(e); }
    <native fn println(...)>

No error, no diagnostic — a silent wrong answer. `rt::RaisedPayload` carries
it now: detached on the task's side while its heap is alive, reattached on the
awaiting side. It mirrors what the native runtime already models explicitly as
`TaskOutcome::{Returned, Raised}`.

**The native channel copy knew one map representation.** A map whose values
are all Int is a typed carrier, not a boxed `DYN_MAP`, and `own()` matched the
boxed tag alone — so it fell through to "value cannot cross a channel". This
is not raise-specific: `send(c, {"code": 7})` failed natively while the
interpreter sent it. The list arm above it already covered every list
representation, with a comment saying why; the map arm now does the same
through `map_entries`, which is the accessor `encoding` already uses for this.

**Int and short-string payloads were always fine**, because they are stored
inline and carry no handle. That is why all three survived: the obvious probe
passes.

`examples/syntax/cross_task_raise.lk` covers each payload representation
across a task, and each map representation across a channel including a struct
(whose identity has to survive too). Reverted locally it fails on the VM with
`assertion failed` and natively with `value cannot cross a channel`.

All nine gates pass: coverage 75/75, sweep 76 identical, geomean 1.025x.
    > fn f(x) { return x; }
    > f(1)
    1
    > f("a")
    Error: Cannot unify Int with String

The same three lines in a file are fine. The checker applies its solved
substitutions to every recorded declaration once a program is checked —
`apply_substitutions_to_environment`, whose own comment says "this runs once,
after the whole program". The REPL checks a *sequence* of programs against one
checker, so the second input's `x = Int` was written back into `f`'s signature
and bound the third.

`FunctionSig::annotated` already states the rule this violated: an unannotated
parameter's type is a derivation from the body, not a claim by the source, so
a call site does not hold it as a requirement. The REPL turned one input's
derivation into a claim over every later input.

A function's signature is now settled by the input that declared it: after
checking an input, functions it did not redeclare are restored to the
signature and recorded type they had going in. A function the input *does*
declare is left alone — its definition and that input's uses were checked
together, exactly as in a file.

This is what made `examples/syntax/match.lk` fail in the REPL: its `shape(v)`
matches one unannotated parameter against six shapes, and the first call
decided which one the rest were allowed to be.

Documented in docs/semantics.md, including the residue this does *not* fix: a
narrowing the body itself performs still carries over, so `fn g(x) { return x
* 2; }` then `g("a")` is rejected in the REPL where a file accepts it —
deliberately, per the same `annotated` rule. Why the file's solver tolerates
`T = Int` and `T = String` together while the REPL's does not is not yet
established.
A default is lowered at the *call site*, because it may refer to the call's
own earlier arguments — `fn f(x: Int, {y: Int = x + 1})` needs `x`, and only
the caller has it. The parameters are bound for that, and every other name in
the default fell through to whatever the caller happened to have in scope:

    const LIMIT: Int = 7;
    fn f({n: Int = LIMIT}) -> Int { return n; }
    fn g() -> Int { let LIMIT = 99; return f(); }

`f()` answered 7 from the top level and 99 from `g`. A silent wrong answer in
a single module, with no import and no concurrency involved, and `lk check`
had nothing to say — the default is written in one function and read in
another.

A default belongs to the declaration. The names it may read are the callee's
own parameters, bound in declaration order, and then module scope. Lowering
now sets the caller's names aside as a unit (`take_name_environment`) and
leaves the module-level tables in place, which is exactly what such an
expression should still see. All four lowering sites go through it — the
window path and the direct-call path each have two.

`examples/syntax/named_default_scope.lk` gates it: a caller shadowing both a
parameter name and a module-level constant, two calls in one such scope (so
they cannot see each other's arguments), and a nested call. Reverted locally
it fails; coverage is 76/76 and both backends agree.

Documented in docs/semantics.md with the part this does not fix: a
cross-module call cannot use defaults at all, because the compiler's signature
table is built from the current program's AST and the runtime path has no
notion of a default. Closing that needs either an import-signature channel or
a callee-side prologue, which is a calling-convention change.
    > struct Reading { zebra: Int, apple: Int, mango: Int, kiwi: Int, pear: Int, fig: Int }
    > Reading { zebra: 1, apple: 2, mango: 3, kiwi: 4, pear: 5, fig: 6 }
    Reading{apple:2,fig:6,kiwi:4,mango:3,pear:5,zebra:1}

Written on one line, in a file, or across a real `use`, the same value prints
in declaration order. A field's declaration order travels with the type, and
both paths that build an instance — `exec::container::declared_type` and
`__lk_make_struct` — read it from the module being executed. Every REPL input
is its own module, so a struct built after the line declaring it had no
declaration to order by and fell back to the field map's own iteration.

The session now gives each input's module the declarations earlier inputs
made, with a redeclaration in the current input winning — what a later
`struct` in a file would do. The module is only cloned when there is something
to add.

This was five of `examples/syntax/struct.lk`'s assertions failing when the
file is fed to the REPL line by line; it is now two, both of them the second
defect below.

Documented in docs/semantics.md alongside the one this does *not* fix: a
function from an earlier input cannot mutate what it is passed —
`fn push_it(xs) { xs.push(9); }` then `push_it(xs)` leaves `xs` as `[1]`,
silently, for lists, maps and structs alike. Modules are isolates and a
crossing deep-copies, which is the right rule for modules and the wrong one
for a session, which is conceptually one program. Closing it means the session
sharing one heap, and the constraint to design against is which globals root
it — not the copy itself.
…nput

    > trait Scaled { fn base(self) -> Int; fn tripled(self) -> Int { return self.w * 3; } }
    > impl Scaled for Rect { fn base(self) -> Int { return self.w; } }
    Error: Method 'tripled' required by trait 'Scaled' not implemented for type 'Rect'

For a method the source never had to write. A trait's default bodies are
copied into the impls that leave them out during *parsing*, over one program's
statement list — and a REPL input carrying the `impl` without the `trait`
beside it never saw them.

The session now carries the defaults each `trait` declares and fills a later
input's impls from them. Applying them after the parse-time pass is harmless:
`fill_impl` already skips a method the impl defines, so an impl's own method
still wins.

This is the same shape as the two before it — a declaration on one input not
reaching the next — and it takes `examples/syntax/struct_trait.lk` fed to the
REPL from several errors down to one. That last one is honest: a default body
that makes a *dynamically dispatched* call (`self.base()`) still meets the
cross-module dispatch rule, because inputs are separate modules. Recorded in
docs/semantics.md next to the mutation limit it shares a root cause with.
The note said the constraint was GC rooting. Checked: it is re-entrancy.
Sharing one `RuntimeModuleState` across inputs fails immediately, because a
cross-module call takes the state *out* of its mutex and sets
`borrowed_for_call` (`take_runtime_callable_state`) — so input 3 calling a
function defined in input 1 meets the very state it is executing against and
gets `ReentrantModule::AlreadyExecuting`.

What has to move is the *heap*, out of `RuntimeModuleState` and up to the
session, leaving the global slots and the borrow flag per module. That is the
`val ↔ vm` boundary CLAUDE.md already describes, approached from the other
side. Recorded so the next attempt starts from the real constraint rather than
rediscovering it.
    use { Pair } from "lib";
    Error: 'Pair' is not an export of this module — no value and no type by
    that name. A `trait` has no constructor to bind, so it cannot be imported
    as a name; a `struct` can, and this module declares neither.

`lib.lk` declares `type Pair = List<Int>;`. The message ended with a fact it
had never checked, and the fact was false — which sends the reader to look for
a typo in a name that is spelled correctly.

It now checks. A name that *is* a trait says so and points at the type that
implements it; anything else says what it could not find (no value, no
`struct` to bind a constructor for) and names the two forms that are
compile-time only, without asserting what the module contains.

Found while sweeping cross-module behaviour against the same code in one file
— struct construction, inherent and trait methods, `typeof`, a returned
closure, a captured closure passed in, `defer`, a `const` read, equality and
`format` all agree across the VM, the single-file VM and the native build, so
this message was the only thing the sweep turned up.
…urement

The note listed two possible fixes and picked neither. Three of the four
candidates are now ruled out by evidence rather than taste:

- Storing defaults as constants in the artifact fails on the corpus itself:
  `fn f(x: Int, {y: Int = x + 1})` means a default may read an earlier
  *parameter*.
- Handing the callee's default expression to the caller to lower is the bug
  just fixed, one level up — the expression would resolve in the *importing*
  module's scope.
- A sentinel for "not supplied" fails because the language distinguishes
  explicit `nil` from omitted: `box(1)` is `1 100` and `box(1, h: nil)` is
  `1 nil`. There is no impossible value to use.
- Calling the callee's default function from the argument-placement code is
  re-entrancy: its `RuntimeModuleState` is already taken (`borrowed_for_call`)
  by the call being set up.

What is left is a callee-side prologue plus a bitmask of which named arguments
were supplied. It evaluates each default in the module that declared it —
making the scope fix structural rather than a caller-side precaution — works
across modules by construction, and keeps `nil` distinct from absent. The cost
is a calling-convention change: compiler, `Function` metadata, artifact
version, AOT lowering and every call path. Not started.
`scripts/verify.sh` is documented as "every gate, one exit code". Compared
against `.github/workflows/` it was running nine of fourteen, and the first
run that included the missing ones failed twice:

- **`lk fmt --check` over the repo.** Six `.lk` files were not in the shape the
  formatter produces. They turned out to be seven stray `defer` probe files at
  the repository root, swept into ab6aac1 by a `git add -A` — the same
  accident the "no build artifacts in the example trees" gate exists for, one
  file extension away from it. Deleted.
- **clippy with `--all-targets`.** Without it clippy never lints test code,
  which is most of what a change adds. Two findings: `lkrt/src/arith.rs` had a
  `pub extern "C"` ABI entry declared *after* its `mod tests`, where it is easy
  to miss and easy to duplicate — moved above; and a deliberate one-element
  loop in `aot/lower/src/tables.rs` now says in a comment why it is written as
  a loop, and allows the lint by name.

Also added, all of which pass: the tracked-artifact check, `LK_GC_STRESS=1`
over the three crates that have host-root discipline, and the artifact
decoder/verifier fuzz — a `.lkm` is an untrusted input to `lk FILE.lkm` and
nothing local was exercising the verifier.

Separately checked and clean: all 75 examples produce identical output under
`LK_GC_STRESS=1`, where every safepoint collects.

The gates left out are the ones needing a toolchain or emulator the script
cannot assume; CLAUDE.md now lists them so the omission is a decision rather
than a gap.
Importing one module transitively and then directly, under `LK_GC_STRESS=1`:

    use { reg } from "lib/dev";      // dev imports bits
    use { low_byte } from "lib/bits";
    Error: heap object 82 out of bounds

The handle is the export map of `lib/bits`, read by `runtime_export_field`
against a heap that had shrunk to 82 entries under it. The map lives in that
module's own heap and nothing else in that heap points at it — the globals
hold the individual values, not the map that collects them — so it was
reachable only as an *extra* root passed by `collect_runtime_export`. Every
other path collected without it, including `RuntimeCallable::collect_garbage`,
which walks an imported module's heap with just the callable's captures.

`RuntimeModuleState` now holds the export as a root of its own, set where a
module's export map is built (a user module in `into_exports`, a stdlib module
in `module.rs`). The order dependence follows from the old shape: with `use
bits` first, the direct import ran before anything could collect that heap.

Found by `scripts/verify.sh` on the first full run after `gc_stress` was added
to it — the gate had been in CI and not locally. It is a pre-existing defect,
not a new one: it reproduces at 44ddf15, before this branch's raise work.

The unit test beside it covers what the existing one did not: a collection
with *no* extra roots, which is every path but one.
A heap holding an imported function reaches *another* module's heap through
it, and the collector followed every such edge with no memory of where it had
been. The module graph is a DAG; walking it as a tree means a module reachable
by K paths is collected K times, and each of those repeats the walk beneath
it.

A REPL session is the shape that makes it explode — every input is its own
module and holds a callable for every earlier one, so the paths multiply with
the session. Cross-module collections, counted:

    8 closures  ->   ~1_000
    12 closures ->  ~20_000
    16 closures -> ~327_000

40 closures under `LK_GC_STRESS=1` did not finish in 200 seconds. Feeding
`examples/syntax/closure.lk` to the REPL under stress did not finish in 300.
Both are now about a tenth of a second, with output identical to the unstressed
run.

The set is keyed on the **callable**, not on the module it belongs to. Keying
on the module is what I wrote first and it is wrong: two callables into one
module carry different captures, a callable's captures live in *that module's*
heap, and skipping the second because the first had been there would leave its
captures unrooted while the heap they live in is swept. Per callable, the work
skipped is work already done with exactly these roots — so this is a strict
reduction in repeated work, not a change to what survives.

Found by widening the GC-stress sweep past the test suite: the examples are
clean under stress, but the same examples fed to the *REPL* under stress were
not, and `closure.lk` was the one that hung.
A stream is an id into a process-global registry *plus* handles: its `roots`
are heap references, and the pipeline the registry holds for that id keeps its
`map`/`filter` callbacks as heap references too. Both belong to the heap that
built them, and a copy between heaps rewrote neither — so the other side got
an id whose callbacks pointed into a heap it could not read.

What that produced depended on when the collector ran:

    // built in one module, consumed in another
    println(stream.collect(stream.map(filtered(), fn(x) => x * x)));
    Error: heap object 102 out of bounds        // plainly
    [1,4,9,16,25,36]                            // with a collection in between

The second is a silent wrong answer — `[16,25,36]` was asked for, and the
filter was skipped because its callback's slot had been reused. In the REPL,
where every input is its own module, it was the normal case: the same pipeline
printed `123456[]`, the digits being the reused slot's `print`.

Refused now, on both copy paths — `copy_runtime_value_with` (a task or a value
handed across) and `exec::imports` (an import, which is also the REPL's path).
Refusing *every* stream would have been the easy rule and the wrong one:
`stream.range(0, 5)` and a list of scalars hold no heap values and cross
soundly. The check is `roots`, which is exactly the set of heap values the
pipeline depends on.

Making it actually cross means rewriting the registry's pipeline into the
destination module — promoting the callbacks to module-carrying callables and
copying the roots — and the registry lives in the stdlib, which `core` must
not reach into. That needs a hook registered by the stream module; written up
in docs/semantics.md rather than half-built here.

Found by running the examples through the *REPL* under `LK_GC_STRESS=1`: as
files they are clean, and `stream_demo.lk` was not.
`InlineCaches` held a call-shape cache and a global-slot cache, both indexed
by pc alone — and a pc is a position inside *one* function, so two functions
with a call (or a global read) at the same pc shared an entry. The second
would have taken the first's call base and argument counts, or the first's
global slot.

That never happened, and only for a reason nothing states: the compiler
records a fact for every call site and every global access it emits, and
artifact v4 serializes them, so the fact branch above the cache always wins.
Instrumented across `examples/`, `bench/` and the cross-boundary probes: the
call cache's read returned a value **zero** times, while every call paid the
write that filled it. The global cache is the same shape.

So it was a wrong answer waiting for a program that reached it — a hand-built
module, an artifact from an older writer, or a future change that drops a
fact — sitting behind a branch that made it unreachable today. Removed rather
than repaired: the two remaining sources are the function's own fact and the
instruction itself, and the instruction is the ground truth.

The index cache stays. It is keyed by pc *and guarded* by the receiver handle
and the heap generation, so a collision misses instead of answering wrongly —
which is the difference this commit is about.

Perf-neutral, measured: interleaved min-of-three on a warm machine, 10M calls,
0.50s before and 0.50s after. The reason to do it is the hazard, not the
write.

Three tests asserted the caches were populated; they were built to check that
a module *without* facts still resolves, which they still check — against the
result, which is what mattered.
`LoadString` gives a `ShortStr` for a constant of 7 bytes or fewer and
allocates a fresh heap object for anything longer — the same immutable
constant, allocated again on every execution. A microbenchmark makes it look
worth fixing: 5M loads in a loop cost 0.07s for a short constant and 0.27s for
a 35-byte one, about 40ns each.

Counted instead of assumed. With a counter on that branch, across the full
workload suite and all 70 programs under `examples/`, the allocating branch is
taken **zero** times: the string constants on hot paths are keys, labels and
short prefixes, all inside seven bytes.

So the fix — a (function, const) → handle cache whose entries have to become
GC roots, as `export_root` just had to — buys nothing measurable and costs
that complexity. Written up in bench/README.md next to the other rejected
optimizations, with the note that any such cache must key on the function
index: the two caches that keyed on pc alone were removed one commit ago for
colliding across functions.
    > use { Pt } from "lib";
    > Pt { x: 1, y: 2 }
    Error: no type named `Pt` is declared here — a struct literal names a type,
    and this module declares none by that name. A type from another module is
    reached … by importing it by name (`use { Pt } from "m";`) …

The message suggests the line that had just been written. In a file the same
two statements work.

Every other entry point seeds the checker with what an import declares
(`typ::seed_imported_signatures`): the CLI for `lk FILE`, the native compiler
for a compile, and `execute_with_ctx_from` for a module loaded as an import.
The session was the one path that did not, so the import bound the constructor
as a value and the *type* stayed unknown. `use * as m from "lib";` with
`m.Pt { … }` always worked, because the namespace spelling does not consult
that table — which is what made this look like a rule about literals rather
than a missing seed.

The session now carries a base directory (the working directory, the same
thing `lk FILE` uses) and seeds each input before checking it.

Found by combining the two axes that had each already found defects: run the
examples through the REPL, and run them across a module boundary. Each on its
own is clean here; together they are not.
    > let out = nums
    Error: Syntax error: Expected Semicolon, found end of input
    > .map(|v| v * 2)
    Error: Syntax error: Unexpected token: Dot

One statement typed over two lines. Continuation was decided on bracket depth
alone, and a method chain closes every bracket it opens on each line, so the
first line looked finished. Three example programs failed in the session for
this and nothing else — `higher_order.lk`, `list_ops.lk` and `macros.lk`, the
last one on a multi-line attribute.

What actually says the input is unfinished is the parser: it ran out of tokens
rather than meeting something wrong. `ParseError::wants_more_input` reports
that, and lives in `core` next to the two `err` helpers that produce the
context it reads — not in the CLI, where a change to the message would go
unnoticed. Two tests pin it in both directions, because the distinction is the
whole point: an input that is *wrong* must not be waited on, or a typo hangs
the session with no way out.

The expression wrapper is tried first, exactly as `execute_input` does, so
`1 + 1` stays a finished input rather than an unfinished statement.

Down from 20 example programs with REPL-only errors to 17. The rest are the
three already written up in docs/semantics.md: named-parameter defaults across
a module boundary, cross-module dispatch and mutation, and streams (which now
refuse rather than answer wrongly).
    let 1 = 2;
    $ lk check   (clean)
    $ lk         Error: Pattern does not match value

`lk check` is documented as the same check the executors run, and a `let`
pattern was not type-checked at all. A `match` arm asks a question, so the
checker deliberately does not constrain the scrutinee to it — right for
`match`, and the reason `let` ended up with nothing: a `let` pattern is a
*requirement*, and the two had been treated as one thing.

Refused now, in the two cases decidable from the value's type:

- A literal pattern. `let 1 = 2;` raised at run time and `let 1 = 1;` ran and
  bound nothing — the only two outcomes it has. A `let` binds names; a literal
  binds none. The message points at `assert` for the check that was meant.
- A destructuring pattern over a definite scalar: `let [a] = 5;`,
  `let { x: v } = 5;`. `String` is excluded, because a list pattern over one
  destructures its characters and the pattern checker already models that.

Deliberately no wider. A destructure whose shape is only known at run time is
ordinary LK and raises there — `let [a, b] = f();` stays accepted, and so does
a literal *nested* in a pattern, which asserts one position. Those three
neighbours are now in the oracle's accept table beside the four refusals,
because a broader rule would take them with it.

The whole corpus still checks clean. Found while writing test inputs for the
REPL continuation fix: `let 1 = 2;` was supposed to be a parse error to test
against, and it parsed.
    let c = chan(1);
    c.close();
    $ lk check   (clean)
    $ lk         Error: Channel has no method 'close'

Every other receiver was already decided at check time — a container by its
method table, a scalar because its surface is empty, a struct by the registry.
A channel and a task were the pair left out, and they are the *easiest* case:
their operations are module functions (`send`, `recv`, `task.await`), so
neither has a method surface at all, and neither has fields a name could fall
back to the way a map's keys do.

Removed on the way: the runtime could read a channel's `capacity`/`type` and a
task's `value` as *properties*, and nothing could reach them. The checker
refuses a field access on either type, and the dynamic route refuses them as
not indexable — instrumented, both arms were dead across every example, every
test and every probe. The spelling the language has is the module function,
`chans.capacity(ch)`, which `concurrency_demo.lk` uses and semantics.md
documents; a second, unreachable spelling is a decoy.

Those two arms were also the only reason the field read went through a
`RuntimeAccess` enum rather than an `Option<RuntimeVal>` — one needed a
payload copied out of another heap, one needed a string allocated, both while
the heap was borrowed. Neither remains, so the enum and its orphaned
`runtime_string_value` helper go with them.

Both directions are in the check oracle: the two refusals, and the module-
function spelling that must keep working.
    use { configure } from "conf";   // fn configure({host: String, timeout_ms: Int? = 1000})
    configure(host: "a");
    $ lk check   (clean)
    $ lk         Error: missing required named argument `timeout_ms`

`timeout_ms` is not required — it has a default. The message was false, and it
arrived at run time, about a call `lk check` had just approved.

The cause is written up in docs/semantics.md and is not fixed here: a default
is materialized by the *compiler*, at the call site, out of the callee's own
declaration — which is what lets it read an earlier argument
(`fn f(x: Int, {y: Int = x + 1})`). A caller in another module does not have
that declaration, and the runtime path that places named arguments has no
notion of a default at all. Closing it needs a callee-side prologue and a mask
of which named arguments were supplied: a calling-convention change.

What is fixed is the lying. The checker knows the callee has a default and now
knows where the signature came from (`SigOrigin`, added for this one rule), so
the call is refused where it is written, with the reason and the way out —
pass the argument explicitly.

The REPL gets it too: a function from an earlier input is in another module,
because every input is compiled as one, so the same rule applies and the same
message appears instead of the runtime's. Five example programs fed to the
session hit this; they now say what is wrong rather than something untrue.

Both directions are in the check oracle, which grew a way to write a case that
needs a second module — the only rule that has one, since it is precisely
about a declaration the caller does not have.
@lollipopkit lollipopkit changed the title feat: AOT try/catch,以及从它牵出来的 210 个提交 feat: consolidate LK language, VM, AOT, and platform support Aug 22, 2026
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.

1 participant