Table of contents
1. The Architecture: Turning Code into Insights
2. Code Property Graphs: The Backbone of Codyze
3. Transferable Rules: Write Once, Audit Everywhere
4. Breaking the Silos with Full-System Analysis
5. Traceability: The “Auditor View”
6. Practical Usage and Walkthrough
7. Looking Ahead: AI-Assisted Mapping
8. Get Started
9. Bibliography
In modern software development, the number and nature of requirements are increasing. Whether it is adhering to the Cyber Resilience Act (CRA), following strict and evolving recommendations on cryptographic algorithms, ensuring that certain features are implemented, or making sure that assumptions about interactions between components of a software system hold – manual checks no longer scale.
As a research group at Fraunhofer AISEC, we recognized that compliance should not be a once-a-year manual hurdle. It should be an automated, integral part of the software development life cycle. This is why we developed Codyze, an open-source analysis framework designed to bridge the gap between high-level (regulatory) cybersecurity requirements and their implementation in source code. Codyze focuses on the requirements that can be traced back to software implementation artifacts, whereas organizational and process-related requirements are out of scope.
Codyze offers the following benefits:
- Codyze is multilingual and supports many programming languages (such as Java, C/C++, Python, Go, TypeScript, Ruby, LLVM-IR, Rust, and C#)
- Highly customizable for product-specific use cases, requirements, and features
- Immediately usable as open-source software
- Analysis of the entire system is possible.
The following sections explain in detail how Codyze works.
1. The Architecture: Turning Code into Insights
The architecture of Codyze can be split into several components:
A so-called Code Property Graph serves as representation of the program. Requirements are modeled in a domain-specific language (DSL), and results are generated and displayed by Codyze. Figure 1 shows a high-level overview of the system. Product-specific inputs and configurations are marked in orange. They are limited to the source code that serves as input to the product-specific requirements, as well as the tagging and related requirements for technologies, frameworks, or libraries that have not previously been considered. The subsequent sections provide more details on the technical implementation of the respective components.
2. Code Property Graphs: The Backbone of Codyze
To analyze software effectively across different languages and frameworks, we avoid looking at text. Instead, our tool Codyze transforms source code into a Code Property Graph (CPG), which serves as an abstraction for the source, merging Abstract Syntax Trees (ASTs), Control Flow Graphs (CFGs), and Data Flow Graphs into a single, searchable representation. The use of Code Property Graphs has proven to be beneficial for language-agnostic security analyses as the combination of sub-graphs minimizes information loss while still providing an abstraction for the actual source code.
Based on the CPG, we implemented several analyses that are often required to reason about program behavior, features, and security properties. Among others, the CPG allows us to perform inter-procedural data-flow analysis, e.g. to track how sensitive data moves across functions. We use typestate analysis to assess whether libraries are used correctly. Value resolution (of constants and value ranges/sets) allows us to identify possible values that variables held during execution. This can be necessary to identify the security posture and configuration of certain libraries.
Because we operate on this unified graph, Codyze is inherently multilingual. We currently support Java, C/C++, Python, Go, TypeScript, Ruby, and LLVM-IR, with Rust and C# support arriving soon. Even with this depth of analysis, performance remains practical. For most repositories where source code is available, the analysis is completed within minutes.
The Scientific Challenge: Our current research focuses on optimizing the scalability of inter-procedural analyses. We continuously improve the performance of our implementation and add scalable analyses so that even large-scale industrial projects can be analyzed in minutes rather than hours.
3. Transferable Rules: Write Once, Audit Everywhere
Beyond representing the source code, Codyze needs to model requirements on a product’s security implementation in an extensible and mostly technology-agnostic way. We translate requirements into concrete security rules that the product must meet. These rules are then formalized in a domain specific language (DSL), which is built using Kotlin. Internally, this DSL translates the user’s requirements to graph queries and triggers the relevant analyses.
We aim to avoid placing an unnecessary burden on compliance officers by requiring them to rewrite the same security rule for every new project solely because the underlying technology or programming language has changed.
Codyze solves this using Transferable Rules: Instead of writing a library-specific rule, the user expresses rules in terms of Concepts and Operations using our formally defined (yet human-readable) domain-specific language. The rules are based on these concepts and operations rather than on the actual code, which can vary greatly depending on the programming language and library. For example, a rule might state that a “CryptographicHash” must use “SHA-256”. We then use a lightweight tagging API to map actual library calls – regardless of the programming language and framework – to that “CryptographicHash” concept. The CPG is enriched with these concepts and operations, and the rules are evaluated against the graph. In fact, this is similar to the way in which standards are designed to be long-lasting and agnostic to specific implementations.
This approach makes Codyze very flexible. We provide rules for the CRA and BSI-TR02102 (cryptography) out of the box. Because Codyze is licensed under Apache License 2.0, you can start testing these open-source rules immediately, while specific custom adaptations can be developed through collaborative projects with Fraunhofer AISEC.
4. Breaking the Silos with Full-System Analysis
Modern software isn’t designed as a monolith – it is a web of microservices. Traditional static analysis tools often stop at the boundary of a single program. Codyze, however, is designed for full-system analysis.
Codyze provides dedicated concepts and operations that model interfaces and connections between components and automatically interconnect them. Once tagging is available for the framework you use, the tool can verify whether requirements are met across the entire system. This enables us to scale Codyze’s checks to the reality of modern software systems, where multiple frameworks and languages must work together securely
5. Traceability: The "Auditor View"
Automation is only valuable if you can trust it. We have moved away from the “black box” approach. Codyze provides full traceability through a dedicated web app featuring an Auditor View.
- Chain of Reasoning: Every conclusion is presented as a tree structure, enabling human auditors to trace a violation back to the exact line of code. They can then validate the findings and either report them to the developers for remediation or mark it as a false positive so that it is suppressed in future analysis runs.
- Explicit Assumptions: Just like other static analysis tools, Codyze relies on certain abstractions to compute results, makes use of heuristics to handle incomplete or ambiguous information in the code, and must make trade-offs in its analyses. In particular, in critical systems, this information can be relevant to an auditor to gain greater confidence in the analysis results and validate them. Codyze therefore explicitly states the assumptions it has made while creating the graph and the results. The assumptions are collected when evaluating the requirements, and those affecting the assessment are displayed to the auditor. In this way, the auditor can accept or reject them.
- Seamless Integration: Results can be exported via SARIF, ensuring that Codyze integrates smoothly with your existing security dashboards and developer tools.
6. Practical Usage and Walkthrough
Now that we have laid out the technical foundation of Codyze, we show you the tool in action for you to better understand how to use it for your projects.
First, let us configure an analysis project and specify a requirement in the DSL. As an example, let us assume that your product is a password manager and that you want to ensure confidentiality of critical data (i.e., accounts and passwords) by encrypting it with state-of-the-art algorithms before persisting it. You can use our existing model for state-of-the-art cryptography and write a rule to assess your requirement (- the implemented checks are still simplified to keep this blog-post short):
requirement {
name = "Encrypted Passwords"
description = "Passwords must be encrypted before persisting them."
fulfilledBy {
with(BSI_TR02102()) {
dataEncryptedBeforePersisting(
isSensitiveData = { it is Password },
isPersistentSink =
{ it is WriteFile || it is DatabaseWrite },
writtenData =
{ (it as? WriteFile)?.what?.let { listOf(it) } ?: (it as? DatabaseWrite)?.parameters },
)
}
}
}
To make this work, we also have to provide the TOE-independent graph query to identify which performs the actual checks. Note that this query is already part of our set of CRA requirements, so in practice you only need to identify your sensitive data and your persistent sinks.
context(translationResult: TranslationResult, cryptoCatalog: CryptoCatalog)
fun dataEncryptedBeforePersisting(
isSensitiveData: (Node) -> Boolean,
isPersistentSink: (Node) -> Boolean,
writtenData: (Node) -> List?
): QueryTree {
return translationResult
.allExtended(isPersistentSink) {
val writtenData =
writtenData(it)
?: return@allExtended QueryTree(
value = true,
stringRepresentation = "Missing data written to a persistent location",
node = it,
operator = GenericQueryOperators.EVALUATE,
)
writtenData
.map { data -> data.alwaysCorrectlyEncrypted(isSensitiveData) }
.mergeWithAll()
.apply {
stringRepresentation =
if (value) "Data is always encrypted before being persisted to the sink."
else "Data is not always encrypted before being persisted to the sink."
}
}
.apply {
stringRepresentation =
if (value)
"For each operation persisting data, the data is always encrypted with state of the art algorithms before being persisted."
else
"For some operations persisting data, the data is not encrypted with state of the art algorithms before being persisted."
}
}
context(cryptoCatalog: CryptoCatalog)
fun Node.alwaysCorrectlyEncrypted(
isSensitiveData: (Node) -> Boolean
): QueryTree {
val relevantEncryptOperations = mutableListOf()
val writtenDataIsEncrypted =
dataFlow(
startNode = this,
direction = Backward(GraphToFollow.DFG),
type = Must,
scope = Interprocedural(),
predicate = { enc ->
if (enc is Encrypt) {
relevantEncryptOperations.add(enc)
true
} else if (!isSensitiveData(enc)) {
// Encryption is not necessary for insensitive data
true
} else {
false
}
},
)
.apply {
stringRepresentation =
if (value) "Data is always encrypted before being persisted."
else "Data is not always encrypted before being persisted."
}
val alwaysSotaEncryption =
relevantEncryptOperations
.map {
// CipherOperation.concept is directly a Cipher
(it.concept as? Cipher)?.conformsToStateOfTheArt()
?: QueryTree(
value = false,
stringRepresentation =
"Missing Cipher concept related to an encryption operation.",
node = it,
operator = GenericQueryOperators.EVALUATE,
)
}
.mergeWithAll()
.apply {
stringRepresentation =
if (value)
"All usages of cryptographic algorithms are compliant with the state of the art."
else
"Some usages of cryptographic algorithms are not compliant with the state of the art."
}
// The data must be encrypted and the encryption must be state of the art
return (writtenDataIsEncrypted and alwaysSotaEncryption).apply {
stringRepresentation =
if (value)
"Data is always encrypted using state-of-the-art cryptographic algorithms before being persisted."
else
"Data is not always encrypted using state-of-the-art cryptographic algorithms before being persisted."
}
}
Now it is time to run Codyze and let it generate the results. A few minutes later, you can browse the results in the UI. The dashboard provides you with a quick overview of the requirements that are met and those that are violated.
In Figure 2, it looks as though the code has an issue, so let us try to understand what exactly went wrong. To do so, we revisit the requirements and each step Codyze performed to produce the results (see Figures 3 and 4). We see that, while we appear to be using state-of-the-art cryptography, there is a path where the password is not encrypted. Indeed, as Figure 5 shows, there is a flag that configures whether we want to encrypt the password or not.
We can now fix this bug and are happy with our updated results.
7. Looking Ahead: AI-Assisted Mapping
We are continually evolving Codyze to make it more accessible. One of the main goals is to minimize the remaining manual effort required for tagging and mapping code to our semantic graph. We believe that the pattern-matching and code understanding capabilities of modern Large Language Models (LLMs) can assist us through AI-assisted mappings. Among other things, we are exploring how LLMs can automatically suggest new, undocumented libraries to our semantic concepts and operations. This will further reduce the manual effort needed to bring new tech stacks under the umbrella of automated compliance.
8. Get Started
We believe compliance should be an enabler, not a bottleneck. Codyze provides the technical depth developers value and the clear, auditable evidence compliance officer need.
Would you like a demo for your team, want to learn more about the technical details, or believe that a solution like Codyze could help you in your daily work? Are you wondering whether your software matches the current CRA requirements or uses cryptography securely? Or do you have a completely different use case in mind and want to explore potential collaborations? Then, just send me an email.
9. Bibliography
Fabian Yamaguchi, Nico Golde, Daniel Arp, and Konrad Rieck. 2014. Modeling and Discovering Vulnerabilities with Code Property Graphs. In Proceedings of the 2014 IEEE Symposium on Security and Privacy (SP ’14). IEEE Computer Society, USA, 590–604. https://doi.org/10.1109/SP.2014.44
Author
Alexander Küchler
Alexander Küchler joined Fraunhofer AISEC as a researcher in 2019 after studying computer science with a focus on computer security at TU München and Eurecom/Télécom Paris. He is the group manager of the Application Security Testing research group, which develops the tool Codyze. Alexander’s research focuses on applying program analysis techniques to full-system security analyses, binary analysis, malware analysis, and mobile security.





