How Denial of Service happens in Node.js devalue and how to fix it
Summary
A high-severity Denial of Service vulnerability (CVE-2026-22774) was discovered in the devalue package used by the exo-dashboard SvelteKit application. Attackers could craft malicious serialized input to trigger excessive resource consumption, potentially rendering the web service unresponsive. The fix upgrades devalue from version 5.5.0 to 5.6.2 across both dashboard/package.json and dashboard/package-lock.json.
Introduction
The exo-dashboard is a SvelteKit-based web application that handles user-facing requests in a production environment. Inside its dependency tree sits devalue — a library designed to serialize and deserialize rich JavaScript values (including things like Map, Set, Date, and circular references) that JSON alone can't handle. It's commonly used in SvelteKit's server-side data loading pipeline.
The problem: devalue version 5.5.0 does not adequately constrain how much work it performs when processing untrusted input. An attacker who can influence the serialized data flowing into devalue's deserialization routines — for example, through a crafted HTTP request body or a malicious server response — can trigger runaway resource consumption. On a web service exposed to the internet, this translates directly to a remotely exploitable Denial of Service.
Trivy's dependency scanner flagged this in dashboard/package-lock.json, where the pinned version 5.5.0 was identified as vulnerable under rule CVE-2026-22774.
The Vulnerability Explained
What is devalue, and where does it run?
devalue is not just a development tool — in SvelteKit applications, it is used at runtime to serialize data returned from load() functions on the server and deserialize it on the client. This means it processes data that can be influenced by external actors: API responses, database content, and in some configurations, direct user input.
The vulnerable version
The vulnerable entry in dashboard/package-lock.json before the fix looked like this:
"node_modules/devalue": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.5.0.tgz",
"integrity": "sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==",
"license": "MIT"
}
In devalue 5.5.0, the deserialization logic does not impose strict limits on the complexity or depth of the input it processes. An attacker can construct a deeply nested or cyclically referenced serialized payload that forces devalue to recurse excessively or allocate unbounded memory while attempting to reconstruct the JavaScript object graph.
A concrete attack scenario
Consider a SvelteKit route that accepts serialized data from a client or an upstream API and passes it through devalue for deserialization. An attacker sends a payload like a deeply nested array structure:
// Pseudocode representation of a malicious devalue payload
["BigArray", [["BigArray", [["BigArray", [...10,000 levels deep...]]]]]]
When devalue 5.5.0 attempts to walk this structure to reconstruct the JavaScript value, it may:
- Exhaust the call stack through unbounded recursion
- Spike CPU usage as it traverses exponentially complex reference graphs
- Consume heap memory allocating intermediate objects for each level
In a Node.js web server handling concurrent requests, a single such request can degrade response times for all users. A sustained stream of such requests can take the service entirely offline — no authentication required.
Why this matters for exo-dashboard specifically
The exo-dashboard is described as a web service where "vulnerabilities in request handlers are directly exploitable by remote attackers." The devalue package is listed as a production dependency (not a devDependency), meaning it is active in the deployed application, not just during the build process. This removes any argument that the vulnerability is theoretical or limited to development environments.
The Fix
What changed
The fix involved two files: dashboard/package.json and dashboard/package-lock.json.
dashboard/package.json — Adding an explicit production dependency:
"dependencies": {
+ "devalue": "^5.6.2",
"highlight.js": "^11.11.1",
"katex": "^0.16.27",
"marked": "^17.0.1",
Previously, devalue was only pulled in transitively (as a dependency of SvelteKit internals) without being explicitly declared in package.json. Adding it explicitly with "^5.6.2" ensures that npm's resolver will always select a patched version, even if a transitive dependency would otherwise resolve to the vulnerable 5.5.0.
dashboard/package-lock.json — Pinning the patched version:
"node_modules/devalue": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.5.0.tgz",
- "integrity": "sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==",
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz",
+ "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==",
"license": "MIT"
}
The lock file update is critical. Without it, npm ci (used in most CI/CD pipelines) would continue installing the vulnerable 5.5.0 regardless of what package.json says, because npm ci respects the lock file exactly. Updating the lock file ensures reproducible, secure installs across all environments.
How the fix works
devalue 5.6.2 introduces internal safeguards that bound the complexity of the input it will process. This includes limits on recursion depth and the total number of nodes it will traverse when reconstructing a JavaScript value from a serialized payload. Inputs that exceed these bounds are rejected with an error rather than being processed indefinitely.
The integrity hash change (sha512-69sM5... → sha512-nPRkj...) confirms that a genuinely different package artifact is being installed — this is not just a metadata change.
Prevention & Best Practices
1. Declare all runtime dependencies explicitly
Even if a package is pulled in transitively, if your application depends on it at runtime, declare it explicitly in dependencies. This gives you control over the version range and makes security upgrades straightforward.
2. Never pass untrusted input directly to deserialization libraries
Deserialization is a high-risk operation. Before passing any external data to libraries like devalue, JSON.parse with revivers, or similar, validate its structure and size:
// Example: reject oversized payloads before deserialization
app.use(express.json({ limit: '10kb' }));
// Or validate structure with a schema library like zod
import { z } from 'zod';
const schema = z.string().max(1000);
const safeInput = schema.parse(rawInput);
3. Scan your lock files in CI
Tools like Trivy, npm audit, and Snyk can scan package-lock.json for known-vulnerable versions. Add these scans as required checks in your CI pipeline so vulnerable dependencies are caught before they reach production:
# Run Trivy on the dashboard directory
trivy fs ./dashboard --exit-code 1 --severity HIGH,CRITICAL
4. Use npm audit regularly
cd dashboard && npm audit
This will surface known CVEs in your dependency tree and suggest upgrade paths. Automate this in your development workflow.
5. Understand the difference between dependencies and devDependencies
Libraries used at runtime (like devalue in a SvelteKit app) belong in dependencies. Scanners and security tools treat these differently — production dependencies get higher scrutiny because they are present in deployed environments.
Security Standards References
- CWE-400: Uncontrolled Resource Consumption — the root cause category for this vulnerability
- OWASP A05:2021 — Security Misconfiguration: includes failing to update components with known vulnerabilities
- OWASP A06:2021 — Vulnerable and Outdated Components: directly applicable to this dependency upgrade scenario
Key Takeaways
devalue5.5.0 is vulnerable in production SvelteKit apps: If yourpackage-lock.jsonpinsdevalueat5.5.0, you are exposed to CVE-2026-22774 in any environment where user-influenced data reaches the deserialization path.- Transitive dependencies need explicit pinning:
devaluewas not originally declared inpackage.json, meaning its version was controlled entirely by SvelteKit's own dependency range. Adding an explicit"devalue": "^5.6.2"entry takes back control. - Lock file updates are not optional: Changing
package.jsonalone is insufficient. Thepackage-lock.jsonmust be regenerated and committed to ensurenpm ciinstalls the patched version in CI/CD and production. - Trivy caught what a code review would miss: The vulnerability was not in application code — it was in a pinned version number inside a lock file. Automated dependency scanning is the only reliable way to catch this class of issue.
- Production dependency classification matters: Because
devalueis independencies(notdevDependencies), it is present in the deployedexo-dashboardservice, making this a directly exploitable remote attack surface.
How Orbis AppSec Detected This
- Source: Untrusted data (user-influenced HTTP request bodies or external API responses) flowing into SvelteKit's data loading pipeline
- Sink:
devalue's deserialization routines innode_modules/devalueversion5.5.0, invoked at runtime within theexo-dashboardweb service - Missing control: No version constraint preventing the installation of the vulnerable
5.5.0artifact; no input size or complexity bounds in the devalue library itself at that version - CWE: CWE-400 — Uncontrolled Resource Consumption
- Fix: Upgraded
devaluefrom5.5.0to5.6.2in bothdashboard/package.jsonanddashboard/package-lock.json, pinning the patched artifact with its verified integrity hash
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
CVE-2026-22774 is a reminder that security vulnerabilities don't only live in the code you write — they live in the versions of libraries you depend on. The exo-dashboard application was exposed to a remotely exploitable Denial of Service simply because devalue 5.5.0 was pinned in its lock file without an explicit version constraint in package.json.
The fix is precise and minimal: two files changed, one version number bumped, and the attack surface is closed. But getting there required knowing the vulnerability existed in the first place — which is exactly the gap that automated dependency scanning fills.
If your Node.js or SvelteKit application uses devalue, check your package-lock.json now. If you see any version below 5.6.2, you are vulnerable.
References
- CWE-400: Uncontrolled Resource Consumption
- OWASP A06:2021 — Vulnerable and Outdated Components
- OWASP Dependency Check Cheat Sheet
- devalue on npm — official package page
- Trivy vulnerability scanner documentation
- Semgrep rules for vulnerable npm dependencies
- fix: upgrade devalue to 5.6.2 (CVE-2026-22774)