You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Apache Fory — evaluation for Stafficy (2026-08-18)
TL;DR
Poisoning/provenance: clean. Stock apache/fory clone at upstream main HEAD 6fedcf1dc (2026-08-18), ASF LICENSE/NOTICE, 128 tags (latest v1.6.1, 2026-08-16), sample-only git hooks, no extra files, no binaries, no submodules. Zero prompt-injection markers, zero credential-shaped strings, zero hidden text (only one U+200D, an emoji ZWJ in a Swift test). Verdict after pass 1: AMBER leaning GREEN — amber only because the repo ships a real agent-guidance corpus (AGENTS.md, CLAUDE.md→symlink, .agents/**, Codex openai.yaml skills) that tells assistants to build/test/benchmark, git fetch an apache remote and auto-commit; ordinary engineering guidance, treated as untrusted data.
Builds and tests in Docker: green.fory-core + fory-json build in <1 min on JDK 21 and 25; 236/236 focused fory-core tests pass on both.
Performance on a Stafficy-shaped payload: Fory Java-mode ≈ 6× Jackson serialize, ≈2× deserialize, 0.67× bytes; fory-json ≈ 4× Jackson serialize / 1.7× deserialize at identical JSON size. JDK 25, in-process, no JMH (numbers below).
Security posture: good defaults (class registration required, depth/size/graph limits, gadget blocklist), explicit threat-model docs; one Java-side CVE (registration bypass, fixed in 1.1.0), the serious ones were pyfory.
Value inside Stafficy today: LOW-TO-MODERATE, and NOT NOW as a dependency. Stafficy has zero JDK serialization; everything is Jackson JSON over HTTP, and the CLI helper is a GraalVM native image on reflection-free jackson-jr. Fory's win is real but lands on surfaces we either don't have (Java-native binary blobs) or haven't measured as serialization-bound (projection endpoints). Recommendation: no adoption plan now; keep as the named candidate for two specific seats (below) and revisit with a bounded spike only if the dashboard-projection read residual (plan 618fa17b) or the next-actions/dischargeIndex payloads (F9b, ~800 rows) turn out to be Jackson-bound after measurement.
1. What Fory is (from its own docs, read as text)
Apache Fory (ex-Fury), ASF top-level project: a multi-language serialization framework — cross-language binary protocol (metadata packing, schema evolution, shared/circular refs, polymorphism), a Java-native mode positioned as a drop-in for JDK serialization / Kryo / Hessian / FST, a Row format with zero-copy partial reads, fory-json (runtime-generated JSON codecs), and a schema compiler. Java uses JIT-generated serializers; other languages static/generated. Languages: Java, Python, C++, Go, Rust, JS/TS, C#, Swift, Dart, Scala, Kotlin. Java coordinates org.apache.fory:fory-core (Maven Central; source tree is 1.7.0-SNAPSHOT, released 1.6.1). Java 8 bytecode with multi-release layers for 17 (GraalVM Feature) and 25 (VarHandle/Unsafe codegen).
Trap found (self-builds only): a fory-core jar built on JDK 21 is Multi-Release: true but has no META-INF/versions/25 layer (the jdk25-multi-release profile activates only on JDK≥25); running it on JDK 25 fails at first serialize with Cannot load generated-code helper class org.apache.fory.builder.VarHandleCodegenSupport. Released jars are built on 25 so Maven Central artifacts are fine; the benchmark used the JDK-25 build.
Benchmark (Stafficy-shaped payload)
Payload: a PlanProjection record (17 fields) holding 50 CoordinationEvent records (room, sequence, UUID string, actor, kind, 8-key Map<String,String>, 4 tags, epoch millis, nested record). Plain Java records, no annotations. 5 warm-up + 10 measured rounds × 20k iterations, in-process, no JMH (indicative, not publication-grade). Deps: fory-core/fory-json 1.7.0-SNAPSHOT (JDK-25 build), jackson-databind 2.18.4.
JDK 21: same ordering (Fory JAVA 140k/59k, COMPATIBLE 194k/58k, XLANG 195k/57k, fory-json 135k/51k, Jackson 36k/29k, JDK 21k/12k). Without --add-opens on JDK 25 the numbers hold but Fory prints a sun.misc.Unsafe::staticFieldOffset terminally-deprecated warning. Differences among Fory modes are within run noise (~10–15%). Records worked in every mode with no annotations; JDK serialization needed a Serializable mirror.
Read: ~6× Jackson on serialize, ~2× on deserialize, two-thirds the bytes; fory-json ~4× / ~1.7× at byte-identical JSON. Deserialize is where the JSON gap is smallest, and deserialize is what most of our read paths do.
GraalVM native-image (relevant to ai-bin)
docs/object-serialization/java/graalvm.md: fory-core ships its own ForyGraalVMFeature (java17 MR layer), auto-activated via META-INF/native-image/org.apache.fory/fory-core/native-image.properties (--features=… + ~760-class --initialize-at-build-time list, small reflection/serialization configs). Pattern: static Fory (or ThreadLocalFory), register() every type, fory.ensureSerializersCompiled(), own bootstrap class in --initialize-at-build-time; serializers generated at image build time (runtime JIT off). Their own GraalVM tests pass -J--add-opens=java.base/java.lang.invoke=ALL-UNNAMED. fory-json has its own Feature (@JsonType, optional @ForyJsonProvider). Claim: ~45× JDK serialization in native image (docs/benchmarks/.../java-graalvm.md).
GraalVM native-image benchmark — does "~45× JDK" reproduce?
No — not on our payload. Same harness/payload, ghcr.io/graalvm/native-image-community:25 (CE 25.0.2, aarch64), released fory-core/fory-json 1.6.1, native-maven-plugin 0.10.6 with the reachability-metadata repo; JDK-ser/Jackson metadata from the tracing agent. JIT and native measured in the same container.
codec
bytes
JIT ser
JIT deser
native ser (r1/r2)
native deser (r1/r2)
JDK ObjectOutputStream
20,745
22,333
13,597
6,808 / 6,547
2,488 / 2,444
Jackson 2.18.4
25,311
41,874
26,888
15,504 / 14,095
14,216 / 13,893
Fory JAVA (single Fory)
17,004
149,834
75,557
20,634 / 21,696
17,825 / 18,726
Fory JAVA (ThreadLocalFory)
17,004
145,314
76,985
20,722 / 19,866
18,644 / 17,909
Fory JAVA COMPATIBLE
17,004
149,862
74,377
21,264 / 20,285
18,407 / 18,266
Fory XLANG
16,238
147,727
84,580
21,094 / 22,066
19,494 / 20,307
fory-json
25,311
118,686
55,229
4,454 / 4,387
4,188 / 3,978
Fory vs JDK in native: 3.1× serialize / 7.4× deserialize (JIT: 6.7× / 5.6×). Fory vs Jackson in native: only 1.4× / 1.3×. Native/JIT for Fory itself: 0.14× ser, 0.24× deser.
Why: in a native image Fory 1.6.1 forces codegen OFF (ForyBuilder.finishCodegen() when IN_GRAALVM_NATIVE_IMAGE; withCodegen(true) is ignored) and runs the interpreted ObjectSerializer for every instance, whereas the JIT uses a generated …ForyCodec class. Fory's own 46× figure is for a 12-primitive-field struct under that same interpreted path — on a string/collection-heavy record graph the JIT-codegen advantage is what evaporates. -O3 -march=native and heap knobs: within noise.
fory-json in native collapses (4.4k ops/s, ~0.3× Jackson) with an explicit warning that generated codecs need a reachable @ForyJsonProvider in the image — configuration burden we did not take on.
Build: native-image 38 s, 50.4 MB binary (25 MB code + 25 MB heap), peak RSS 2.9 GB. Build-time-init pitfalls hit and fixed: ForyJson$PooledState, ThreadPoolFory$PooledEntry and config.Language must not be reachable from build-time-initialized statics (use ThreadLocalFory, create JSON at run time, add Language,CompatibleMode to --initialize-at-build-time). Runtime warnings: COMPATIBLE mode falls back to CompatibleSerializer ("can't generate class at runtime in graalvm").
In native, Fory beats Jackson by ~1.3–1.4× on this shape and fory-json is slower than Jackson unless codecs are pre-registered; Seat A (fory-json on the JVM server's hot projection endpoints) is unaffected by this addendum — that is a JIT question, and stays behind the measurement gate.
Artifacts: 2026-08-18-apache-fory-evaluation-artifacts/native/ (TABLE.txt, raw runs, build logs) and bench-native-src/.
Security posture (docs + ForyBuilder defaults, read as text)
Defaults: requireClassRegistration=true, maxDepth=50, maxGraphMemoryBytes=128 MiB, maxUnbackedContainerItems=8192, maxTypeFields=512, maxTypeMetaBytes=4096, checkJdkClassSerializable=true, trackingRef=false, and xlang=true by default — Fory.builder().build() is XLANG mode; Java-native needs withXlang(false)/Language.JAVA. Easy foot-gun.
Untrusted input: keep registration on; if off, pair with withTypeChecker(AllowListChecker …), withDeserializeUnknownClass(false); built-in gadget blocklist (resolver/DisallowedList.java, ~258 names). Java-native mode honours writeObject/readObject/writeReplace/readResolve on registered classes — registration is the trust decision ("not a sandbox for application-owned types", docs/security/threat-model.md). docs/security/deserialization.md states invariants (no OOM from declared lengths, depth limit, no no-progress loops, cleanup after failed root).
CVEs (fory.apache.org/security): CVE-2026-50076 Important — Java deser bypass of registration/TypeChecker/DisallowedList in fory-core <1.1.0 (fixed 1.1.0); CVE-2026-48207 pyfory policy bypass; CVE-2025-61622 Critical pyfory pickle-fallback RCE; CVE-2025-59328 Moderate DoS 0.5.0–0.12.1. Nothing open against 1.6.x Java per that page.
A. fory-json as the JSON codec on hot projection endpoints (coordination dashboard projection, next-actions rows ~800, dischargeIndex ~1250 entries)
~4× serialize / ~1.7× deserialize vs Jackson at identical bytes; Spring MVC HttpMessageConverter swap is contained
Two JSON codecs in one app; @JsonType annotations on projection records; Jackson-specific annotations/mixins in those 55 sites would not carry; native-image irrelevant (server is JVM). Unmeasured premise: we do not know serialization is a material share of those requests (RequestTimingFilter slow-request events exist — measure first).
Candidate. Not now. Spike only if measurement shows JSON encode/decode > ~20% of a hot endpoint.
B. Fory binary for internal byte blobs (plan-branch snapshots, agent-state bundles, dashboard cursors)
0.67× bytes, faster; CompatibleMode for schema evolution
Blobs become opaque in the metastore (JSON is inspectable/greppable — a real operator property here); registration + version discipline; another persisted format to carry forever; encryption/fingerprint code assumes JSON bytes
No. Inspectability > size for these.
C. ai-bin native helper
GraalVM story is real (Feature auto-registered)
Envelope must stay JSON to talk to the server; jackson-jr already reflection-free; would add a ~MB dependency and build-time registration to a CLI whose bottleneck is HTTP + git, not JSON
No.
D. Cross-language exchange (Python/JS peers)
one binary schema
our peers integrate over HTTP JSON by design (curl-able, human-readable receipts, room notes)
No.
Decision: treat Fory as known-clean and known-fast, file no adoption plan, and record it as the named candidate for seat A behind a measurement gate. If 618fa17b (dashboard-projection read residual) or the F9b payload sizes ever make Jackson the bottleneck, the bounded spike is: fory-jsonHttpMessageConverter for the two projection endpoints only, @JsonType on their record types, A/B behind a property, measured with the existing RequestTimingFilter. Java 25 note for that spike: pass --add-opens=java.base/java.lang.invoke=ALL-UNNAMED (or accept the Unsafe deprecation warning); use released 1.6.x jars (built on JDK 25), never a JDK-21 self-build.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Apache Fory — evaluation for Stafficy (2026-08-18)
TL;DR
Poisoning/provenance: clean. Stock
apache/foryclone at upstreammainHEAD6fedcf1dc(2026-08-18), ASF LICENSE/NOTICE, 128 tags (latest v1.6.1, 2026-08-16), sample-only git hooks, no extra files, no binaries, no submodules. Zero prompt-injection markers, zero credential-shaped strings, zero hidden text (only one U+200D, an emoji ZWJ in a Swift test). Verdict after pass 1: AMBER leaning GREEN — amber only because the repo ships a real agent-guidance corpus (AGENTS.md,CLAUDE.md→symlink,.agents/**, Codexopenai.yamlskills) that tells assistants to build/test/benchmark,git fetchanapacheremote and auto-commit; ordinary engineering guidance, treated as untrusted data.Builds and tests in Docker: green.
fory-core+fory-jsonbuild in <1 min on JDK 21 and 25; 236/236 focused fory-core tests pass on both.Performance on a Stafficy-shaped payload: Fory Java-mode ≈ 6× Jackson serialize, ≈2× deserialize, 0.67× bytes;
fory-json≈ 4× Jackson serialize / 1.7× deserialize at identical JSON size. JDK 25, in-process, no JMH (numbers below).Security posture: good defaults (class registration required, depth/size/graph limits, gadget blocklist), explicit threat-model docs; one Java-side CVE (registration bypass, fixed in 1.1.0), the serious ones were pyfory.
Value inside Stafficy today: LOW-TO-MODERATE, and NOT NOW as a dependency. Stafficy has zero JDK serialization; everything is Jackson JSON over HTTP, and the CLI helper is a GraalVM native image on reflection-free jackson-jr. Fory's win is real but lands on surfaces we either don't have (Java-native binary blobs) or haven't measured as serialization-bound (projection endpoints). Recommendation: no adoption plan now; keep as the named candidate for two specific seats (below) and revisit with a bounded spike only if the dashboard-projection read residual (plan 618fa17b) or the next-actions/dischargeIndex payloads (F9b, ~800 rows) turn out to be Jackson-bound after measurement.
1. What Fory is (from its own docs, read as text)
Apache Fory (ex-Fury), ASF top-level project: a multi-language serialization framework — cross-language binary protocol (metadata packing, schema evolution, shared/circular refs, polymorphism), a Java-native mode positioned as a drop-in for JDK serialization / Kryo / Hessian / FST, a Row format with zero-copy partial reads,
fory-json(runtime-generated JSON codecs), and a schema compiler. Java uses JIT-generated serializers; other languages static/generated. Languages: Java, Python, C++, Go, Rust, JS/TS, C#, Swift, Dart, Scala, Kotlin. Java coordinatesorg.apache.fory:fory-core(Maven Central; source tree is1.7.0-SNAPSHOT, released 1.6.1). Java 8 bytecode with multi-release layers for 17 (GraalVM Feature) and 25 (VarHandle/Unsafe codegen).2. Pass 1 — poisoning-aware read-only audit (evidence)
-- Passed.
3. Pass 2 — throwaway Docker build, tests, benchmark
Work dir: session scratchpad
fory-eval/(results kept; source copy and containers/images removed). Containersfory-eval-*only; the Stafficy stack untouched.mvn install -pl fory-core,fory-json -am -DskipTestsForyTest, ForyBuilderTest, MetaShareCompatibleTest, ThreadSafeForyTest, CyclicTest, StreamTest)Trap found (self-builds only): a fory-core jar built on JDK 21 is
Multi-Release: truebut has noMETA-INF/versions/25layer (thejdk25-multi-releaseprofile activates only on JDK≥25); running it on JDK 25 fails at first serialize withCannot load generated-code helper class org.apache.fory.builder.VarHandleCodegenSupport. Released jars are built on 25 so Maven Central artifacts are fine; the benchmark used the JDK-25 build.Benchmark (Stafficy-shaped payload)
Payload: a
PlanProjectionrecord (17 fields) holding 50CoordinationEventrecords (room, sequence, UUID string, actor, kind, 8-keyMap<String,String>, 4 tags, epoch millis, nested record). Plain Java records, no annotations. 5 warm-up + 10 measured rounds × 20k iterations, in-process, no JMH (indicative, not publication-grade). Deps: fory-core/fory-json 1.7.0-SNAPSHOT (JDK-25 build), jackson-databind 2.18.4.JDK 25 (eclipse-temurin:25.0.3,
--add-opens=java.base/java.lang.invoke=ALL-UNNAMED)requireClassRegistration, registered)ThreadSafeForyCompatibleMode.COMPATIBLEJDK 21: same ordering (Fory JAVA 140k/59k, COMPATIBLE 194k/58k, XLANG 195k/57k, fory-json 135k/51k, Jackson 36k/29k, JDK 21k/12k). Without
--add-openson JDK 25 the numbers hold but Fory prints asun.misc.Unsafe::staticFieldOffsetterminally-deprecated warning. Differences among Fory modes are within run noise (~10–15%). Records worked in every mode with no annotations; JDK serialization needed aSerializablemirror.Read: ~6× Jackson on serialize, ~2× on deserialize, two-thirds the bytes; fory-json ~4× / ~1.7× at byte-identical JSON. Deserialize is where the JSON gap is smallest, and deserialize is what most of our read paths do.
GraalVM native-image (relevant to
ai-bin)docs/object-serialization/java/graalvm.md: fory-core ships its ownForyGraalVMFeature(java17 MR layer), auto-activated viaMETA-INF/native-image/org.apache.fory/fory-core/native-image.properties(--features=…+ ~760-class--initialize-at-build-timelist, small reflection/serialization configs). Pattern: staticFory(orThreadLocalFory),register()every type,fory.ensureSerializersCompiled(), own bootstrap class in--initialize-at-build-time; serializers generated at image build time (runtime JIT off). Their own GraalVM tests pass-J--add-opens=java.base/java.lang.invoke=ALL-UNNAMED.fory-jsonhas its own Feature (@JsonType, optional@ForyJsonProvider). Claim: ~45× JDK serialization in native image (docs/benchmarks/.../java-graalvm.md).GraalVM native-image benchmark — does "~45× JDK" reproduce?
No — not on our payload. Same harness/payload,
ghcr.io/graalvm/native-image-community:25(CE 25.0.2, aarch64), released fory-core/fory-json 1.6.1, native-maven-plugin 0.10.6 with the reachability-metadata repo; JDK-ser/Jackson metadata from the tracing agent. JIT and native measured in the same container.ForyBuilder.finishCodegen()whenIN_GRAALVM_NATIVE_IMAGE;withCodegen(true)is ignored) and runs the interpretedObjectSerializerfor every instance, whereas the JIT uses a generated…ForyCodecclass. Fory's own 46× figure is for a 12-primitive-field struct under that same interpreted path — on a string/collection-heavy record graph the JIT-codegen advantage is what evaporates.-O3 -march=nativeand heap knobs: within noise.@ForyJsonProviderin the image — configuration burden we did not take on.ForyJson$PooledState,ThreadPoolFory$PooledEntryandconfig.Languagemust not be reachable from build-time-initialized statics (useThreadLocalFory, create JSON at run time, addLanguage,CompatibleModeto--initialize-at-build-time). Runtime warnings: COMPATIBLE mode falls back toCompatibleSerializer("can't generate class at runtime in graalvm").In native, Fory beats Jackson by ~1.3–1.4× on this shape and fory-json is slower than Jackson unless codecs are pre-registered; Seat A (fory-json on the JVM server's hot projection endpoints) is unaffected by this addendum — that is a JIT question, and stays behind the measurement gate.
Artifacts:
2026-08-18-apache-fory-evaluation-artifacts/native/(TABLE.txt, raw runs, build logs) andbench-native-src/.Security posture (docs +
ForyBuilderdefaults, read as text)requireClassRegistration=true,maxDepth=50,maxGraphMemoryBytes=128 MiB,maxUnbackedContainerItems=8192,maxTypeFields=512,maxTypeMetaBytes=4096,checkJdkClassSerializable=true,trackingRef=false, andxlang=trueby default —Fory.builder().build()is XLANG mode; Java-native needswithXlang(false)/Language.JAVA. Easy foot-gun.withTypeChecker(AllowListChecker …),withDeserializeUnknownClass(false); built-in gadget blocklist (resolver/DisallowedList.java, ~258 names). Java-native mode honourswriteObject/readObject/writeReplace/readResolveon registered classes — registration is the trust decision ("not a sandbox for application-owned types",docs/security/threat-model.md).docs/security/deserialization.mdstates invariants (no OOM from declared lengths, depth limit, no no-progress loops, cleanup after failed root).maxDepth20, 128 MiB graph budget, buffer size limit.4. Value assessment
fory-jsonas the JSON codec on hot projection endpoints (coordination dashboard projection, next-actions rows ~800, dischargeIndex ~1250 entries)HttpMessageConverterswap is contained@JsonTypeannotations on projection records; Jackson-specific annotations/mixins in those 55 sites would not carry; native-image irrelevant (server is JVM). Unmeasured premise: we do not know serialization is a material share of those requests (RequestTimingFilterslow-requestevents exist — measure first).CompatibleModefor schema evolutionai-binnative helperDecision: treat Fory as known-clean and known-fast, file no adoption plan, and record it as the named candidate for seat A behind a measurement gate. If 618fa17b (dashboard-projection read residual) or the F9b payload sizes ever make Jackson the bottleneck, the bounded spike is:
fory-jsonHttpMessageConverterfor the two projection endpoints only,@JsonTypeon their record types, A/B behind a property, measured with the existingRequestTimingFilter. Java 25 note for that spike: pass--add-opens=java.base/java.lang.invoke=ALL-UNNAMED(or accept the Unsafe deprecation warning); use released 1.6.x jars (built on JDK 25), never a JDK-21 self-build.All reactions