How Cal.com Rebuilt AppSec After Going Closed Source
How Cal.com consolidated noisy security tooling into one continuous, context-aware pull request security program with Gecko.
Gecko Security

Your Maven or npm project contains way more packages than you remember adding. That’s because transitive dependencies in Maven and other package managers automatically pull in nested libraries whenever you import something new. The concept is straightforward: if your code depends on Package A, and Package A depends on Package B, then B becomes your transitive dependency. The same principle exists in database normalization, where it describes non-key attributes depending on other non-key attributes instead of the primary key. Both scenarios create indirect relationships that cause specific problems: data anomalies in databases, and security blind spots in your software supply chain. We’ll show you how to visualize these dependency chains, identify vulnerabilities buried several layers deep, and apply practical strategies to reduce your attack surface.
TLDR:
A transitive dependency exists when one element depends on a second element, which in turn depends on a third element, creating an indirect relationship between the first and third. If A depends on B, and B depends on C, then A transitively depends on C.
This concept appears in two distinct contexts: database design and software package management. In databases, transitive dependency refers to relationships between attributes that violate normalization principles. In package management, it describes the chain of dependencies between code libraries.
The database version deals with data structure. When a non-key column depends on another non-key column instead of the primary key, you have a transitive dependency that causes data anomalies and redundancy. The software version deals with your supply chain. When your application imports a library that imports other libraries, those downstream packages become your transitive dependencies.
In database design, transitive dependencies occur when attribute C depends on primary key A only through a non-key attribute B (A → B → C).
Consider employee records with EmployeeID, Department, and DepartmentLocation. Since EmployeeID determines Department and Department determines DepartmentLocation, the location depends on EmployeeID indirectly.
This structure causes three problems. Data redundancy repeats “Building 3” for every Sales employee. Update anomalies require changing multiple rows when relocating a department. Deletion anomalies lose location data when removing the last departmental employee.
The solution splits the table. One stores EmployeeID with Department. Another maps Department to DepartmentLocation. Each non-key attribute now depends directly on its table’s primary key.
Here’s a book inventory system showing transitive dependency:
| BookID | Title | GenreID | Genre | GenreCategory |
|---|---|---|---|---|
| 1 | Neuromancer | 101 | Cyberpunk | Science Fiction |
| 2 | Snow Crash | 101 | Cyberpunk | Science Fiction |
| 3 | Foundation | 102 | Space Opera | Science Fiction |
BookID determines GenreID, and GenreID determines GenreCategory. The category depends on BookID only through GenreID, creating redundancy across all cyberpunk entries. Breaking this chain requires separating Genre and GenreCategory into distinct tables.
Another example uses orders:
| OrderID | CustomerID | CustomerCity | RegionCode |
|---|---|---|---|
| 5001 | 200 | Seattle | PNW |
| 5002 | 201 | Portland | PNW |
OrderID determines CustomerCity, which determines RegionCode. Moving a customer requires updating every order row.
Third Normal Form requires that every non-key attribute depends directly on the primary key, not through another non-key attribute.
A table reaches 3NF when it satisfies Second Normal Form (no partial dependencies) and contains no transitive dependencies. The decomposition process follows three steps:
First, identify the functional dependency chain where primary key → attribute X → attribute Y. Second, create a new table for the transitive relationship with attribute X as the primary key and attribute Y as its dependent. Third, replace the transitively dependent attribute in the original table with a foreign key reference.
This decomposition prevents data anomalies while preserving relationships through foreign keys.
In software development, transitive dependencies describe how package managers automatically pull in nested libraries. When you add a package to your project, you inherit all of its dependencies, plus their dependencies, creating chains that can extend dozens of levels deep.
If your application imports Library A, which requires Library B, which needs Library C, then C becomes your transitive dependency. You never declared it directly, but it ships in your codebase. Package managers like Maven, npm, and Gradle handle this resolution automatically through their dependency trees.
The problem is visibility. Most developers track their direct dependencies but rarely review what those packages bring along. Your dependency tree can contain hundreds of packages you’ve never heard of, each representing potential security exposure.
Maven provides the dependency:tree command to visualize your dependency hierarchy. Running mvn dependency:tree outputs every package your project inherits, showing which direct dependency introduced each transitive one.
To exclude unwanted packages, add an <exclusions> block within your dependency declaration:
<code class="language-xml"><dependency>
<groupId>com.example</groupId>
<artifactId>library-a</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>org.unwanted</groupId>
<artifactId>problematic-lib</artifactId>
</exclusion>
</exclusions>
</dependency>
</code>Version conflicts occur when different dependencies pull in incompatible versions of the same library. Maven’s <dependencyManagement> section forces specific versions across your project, overriding what transitive dependencies request.
npm resolves transitive dependencies by creating a nested node_modules structure where each package carries its own dependencies. The package-lock.json file locks exact versions across your tree, preventing unexpected updates during installation.
To view your dependency hierarchy, run npm list. For transitive vulnerabilities, npm audit scans your tree and reports known security issues with severity ratings.
npm version 8.3+ introduced overrides, letting you force specific versions of transitive dependencies:
<code class="language-json">{
"overrides": {
"vulnerable-lib": "2.1.4"
}
}
</code>This replaces all instances of the specified package throughout your tree, patching vulnerabilities without waiting for upstream maintainers to update their dependencies.
Transitive dependencies expand your attack surface because they receive minimal security review. While teams audit direct dependencies, nested packages slip through unexamined. Attackers exploit this gap by compromising obscure libraries buried deep in dependency trees.
Dependency confusion attacks inject malicious packages with identical names into public repositories, tricking package managers into pulling compromised code. 13% of Log4j downloads in 2025 were vulnerable versions, showing how outdated transitive dependencies persist after patches exist.
The problem compounds when vulnerabilities sit multiple layers deep. Your direct dependencies update quickly, but their dependencies lag behind, leaving you exposed through chains you never explicitly chose.
Start by generating a Software Bill of Materials (SBOM) using tools like CycloneDX or SPDX. These export your complete dependency graph in standardized formats that security scanners can process.
Track dependency depth to identify packages buried multiple layers down. Set depth thresholds in your CI pipeline to flag when dependencies exceed safe review limits. Anything beyond three or four levels typically escapes manual audit.
Automate scanning with tools that cross-reference your SBOM against vulnerability databases. Schedule weekly scans instead of only checking during releases, since new CVEs appear constantly against existing dependencies.
Lock your dependency versions explicitly instead of accepting version ranges. This prevents unexpected updates from introducing compromised packages during routine installs.
Integrate vulnerability scanning directly into your CI/CD pipeline to catch issues before deployment. Configure builds to fail when high-severity vulnerabilities appear in your dependency tree, forcing immediate resolution.
Apply cooldown periods before adopting new package versions. Supply chain attacks typically exploit exposure windows of 4 to 5 hours after malicious code reaches repositories. Waiting 72 hours before updating gives the security community time to identify and report compromised releases.
Review your dependency graph quarterly to prune unused packages. Every transitive dependency carries risk. Remove direct dependencies you no longer need, which automatically eliminates their transitive chains.
Set up automated alerts when new CVEs affect your dependency tree, then rank patches by exploitability instead of just severity scores.
Traditional SAST tools analyze dependencies for known vulnerabilities but miss business logic flaws that surface when execution flows across multiple libraries and services. They can’t answer whether authorization checks persist through transitive code paths or if trust boundaries break down between dependency layers.
Gecko’s semantic indexing traces how data moves through your dependency chain, following execution across repositories and third-party libraries. When your code calls a library that calls another service, we build the complete call graph to identify where security controls disappear.
Our Code Property Graph connects context from your codebase, dependencies, and infrastructure to surface multi-step vulnerabilities that require understanding how trust flows through your stack.
You can’t escape transitive dependencies in modern development, but you can control how they affect your systems. In databases, Third Normal Form removes them cleanly through decomposition. In software, the answer is continuous monitoring and explicit version control across your entire dependency tree. Set up automated scanning, review your graphs quarterly, and treat transitive packages with the same scrutiny you give direct dependencies.

Artemiy Malyshau
Co-founder & CTO
Artemiy served in an elite unit of the Austrian Cyber Forces, defending national infrastructure He was then the first employee at a government-backed cybersecurity research group, where he led security projects for Interpol and national governments. At Gecko he builds the platform trusted to sit inside Fortune 500 codebases, and holds it to the standard those governments taught him.
The latest news, technologies, and resources from our team.
How Cal.com consolidated noisy security tooling into one continuous, context-aware pull request security program with Gecko.
Gecko Security
Authorization bypass in n8n’s dynamic-credentials OAuth endpoints allows any authenticated user to operate on another user’s OAuth credential by supplying its ID, enabling unauthorized OAuth rebinding and revocation.
Artemiy Malyshau
An IDOR vulnerability in n8n’s public variables API allows authenticated users to read project variables outside their authorized scope, exposing secrets across project boundaries.
Artemiy Malyshau
Learn API scanning for automated security testing. Find vulnerabilities from broken authentication to business logic flaws in your endpoints.
Artemiy Malyshau
A complete guide to automated pentest tools and best practices. Learn what works, what doesn’t, and how to implement continuous security testing.
Artemiy Malyshau
Compare the best AI-powered application security testing tools. Find which tools detect business logic flaws and broken access control.
Artemiy Malyshau
Occasional updates, new content, and insights. No spam; unsubscribe anytime.