Back to Blog
critical SEVERITY6 min read

How Missing API Authentication Happens in Node.js and How to Fix It

The GitHub API integration in `src/github.mjs` was making unauthenticated requests, subjecting the application to GitHub's strict 60 requests/hour rate limit. This fix adds secure authentication token injection from environment variables using conditional header spreading, enabling authenticated requests with a much higher rate limit (5,000 requests/hour).

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 7, 2026•Reviewed September 7, 2026

Answer Summary

This is a missing authentication vulnerability in Node.js GitHub API integration (CWE-306: Missing Authentication for Critical Function). The `src/github.mjs` file's `DEFAULT_HEADERS` object lacked any mechanism to inject GitHub authentication tokens, forcing all API calls into the unauthenticated rate limit tier. The fix uses conditional object spreading to inject a Bearer token from the `GITHUB_TOKEN` environment variable, enabling the application to authenticate with GitHub and bypass rate limiting constraints.

Vulnerability at a Glance

cweCWE-306 (Missing Authentication for Critical Function)
fixConditional environment variable token injection via object spreading syntax
riskAPI rate limiting, service degradation, denial of service
languageJavaScript (Node.js)
root causeDEFAULT_HEADERS object in src/github.mjs lacks token injection mechanism
vulnerabilityMissing Authentication for API Integration

Introduction

In the src/github.mjs file, a Node.js module handling GitHub API requests, the application was making API calls without any authentication mechanism. While no hardcoded tokens or obvious security flaws existed in the examined code, the DEFAULT_HEADERS object—which sets the standard headers for all GitHub API requests—was missing a critical piece: any way to inject authentication tokens.

This wasn't a complicated flaw. It was elegantly simple: when developers constructed HTTP headers for GitHub API integration, they specified the user agent, API version, and content type, but provided no path for authentication credentials to flow from the environment into the request headers. This forced every single API request through GitHub's unauthenticated tier, which allows only 60 requests per hour.

For any application making regular API calls, this becomes a bottleneck. Rate limits are hit quickly. Service degrades. And the fix? It turns out to be just as simple as the problem.

The Vulnerability Explained

The Problem

Let's look at the original vulnerable code from src/github.mjs:

const DEFAULT_HEADERS = {
    "user-agent": "GreedySearch/1.0",
    accept: "application/vnd.github+json",
    "x-github-api-version": "2022-11-28",
};

At first glance, this looks reasonable. Standard headers for API communication. But notice what's not there: an Authorization header.

When this application makes any request to the GitHub API using these headers, GitHub sees an unauthenticated request. The API endpoint processes it as a guest request, applying the unauthenticated rate limit of 60 requests per hour per IP address. For a production application, this is crippling.

Why This Matters

The GitHub API publicly documents two tiers:
- Unauthenticated requests: 60 requests/hour
- Authenticated requests: 5,000 requests/hour (for personal access tokens)

That's an 83x difference in capacity. An application that should be able to handle hundreds of API calls instead gets starved after a handful.

Real-World Attack Scenario

Consider how this vulnerability plays out in practice:

  1. An attacker (or even legitimate high-traffic conditions) triggers your application to make GitHub API calls—fetching repository metadata, checking user information, or listing issues.

  2. Your application makes 61 unauthenticated requests in rapid succession to GitHub.

  3. GitHub responds with HTTP 403 (rate limit exceeded) for requests 61 onward.

  4. Your application either fails, returns stale data, or crashes entirely.

  5. If this is a microservice, cascading failures occur. If it's a user-facing feature, the service becomes unavailable.

This isn't a data breach vulnerability, but it's a denial of service vulnerability—one that's trivially exploitable simply by using your own application normally under load.

The Chain Complexity

This vulnerability represents a 2-step weakness chain:
1. Missing authentication mechanism (no token injection)
2. Rate limit threshold exceeded (leading to service disruption)

While not exploitable independently through code injection, this pattern is exactly what automated exploit tools look for: a preventable failure point that creates a serviceable attack vector.

The Fix

The security team implemented a surgical fix using JavaScript's conditional object spreading syntax:

Before (Vulnerable)

const DEFAULT_HEADERS = {
    "user-agent": "GreedySearch/1.0",
    accept: "application/vnd.github+json",
    "x-github-api-version": "2022-11-28",
};

After (Fixed)

const DEFAULT_HEADERS = {
    "user-agent": "GreedySearch/1.0",
    accept: "application/vnd.github+json",
    "x-github-api-version": "2022-11-28",
    ...(process.env.GITHUB_TOKEN
        ? { authorization: `Bearer ${process.env.GITHUB_TOKEN}` }
        : {}),
};

How This Solves the Problem

The fix uses a ternary operator with object spreading to conditionally inject the authorization header:

  • Check: process.env.GITHUB_TOKEN — does the environment variable exist?
  • If yes: Spread { authorization:Bearer ${process.env.GITHUB_TOKEN}} into the headers object
  • If no: Spread an empty object {}, leaving headers unchanged

This pattern is elegant because:

  1. No code path changes — The application works identically whether the token is present or not
  2. Secure credential handling — The token comes from environment variables, not hardcoded strings or configuration files
  3. Standard authentication format — Uses the Bearer token scheme that GitHub's API expects
  4. Graceful degradation — If the environment variable isn't set, the application still works (but with rate limiting)

The Security Improvement

With this fix deployed:

  • Deployments that set GITHUB_TOKEN in their environment automatically authenticate with GitHub
  • Authenticated requests jump from 60 requests/hour to 5,000 requests/hour
  • The application gains resilience against rate-limit-based denial of service
  • No hardcoded credentials are embedded in the source code
  • The fix is backward compatible—systems without the token still function

Key Takeaways

  • Unauthenticated API requests silently degrade service: The DEFAULT_HEADERS in src/github.mjs had no auth mechanism, dropping your rate limit from 5,000 to 60 requests/hour—an 83x reduction.

  • Conditional object spreading securely injects credentials: The fix uses JavaScript's ...(condition ? {...} : {}) pattern to inject Bearer tokens from environment variables only when available.

  • Environment variables are the secure default: Never hardcode API credentials; always flow them from process.env at runtime.

  • Test both authenticated and unauthenticated code paths: Verify your application degrades gracefully when credentials are missing, but functions optimally when they're provided.

  • API rate limits are often a symptom, not a root cause: If your application hits rate limits unexpectedly, check whether authentication is actually being used.

How Orbis AppSec Detected This

Source: GitHub API integration code in src/github.mjs that constructs HTTP requests without credential injection

Sink: The DEFAULT_HEADERS object initialization, which hardcodes static headers without any mechanism to inject authentication tokens from environment variables

Missing control: Conditional header injection logic that would pull GITHUB_TOKEN from the environment and add it to the Authorization header

CWE: CWE-306: Missing Authentication for Critical Function

Fix: Added conditional object spreading (...(process.env.GITHUB_TOKEN ? { authorization:Bearer ${process.env.GITHUB_TOKEN}} : {})) to inject the Bearer token into DEFAULT_HEADERS when the environment variable is set.

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

API authentication isn't glamorous, but it's fundamental. The fix to src/github.mjs demonstrates that even small oversights—a missing conditional, a forgotten environment variable—can create measurable operational and security consequences.

By adopting the pattern shown here—conditional header injection from environment variables—you protect your applications from rate limiting, ensure resilience under load, and maintain secure separation between code and credentials.

The lesson applies beyond GitHub: any external API integration should flow authentication credentials from secure sources into request headers dynamically, never statically. With modern JavaScript, this is trivial to implement. Make it a standard practice.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #60

Related Articles

high

CVE-2026-104850: MCP TypeScript SDK OAuth Credential Leak

The MCP TypeScript SDK (`@modelcontextprotocol/sdk`) contained a flaw in its OAuth client flow where the MCP server being connected to could influence which authorization server the client talked to, meaning client credentials and token-exchange traffic could be directed to an endpoint the attacker controls. This project was pinned to the affected `1.30.0`; the dependency has been moved to `^1.31.0`, which carries the upstream fix for CVE-2026-104850. Any MCP client that performs OAuth against t

high

SQLite Auth Database Plaintext Storage in InitAuthDB()

The `InitAuthDB()` function opened SQLite databases without restricting filesystem permissions, leaving bcrypt password hashes, session tokens, and user settings exposed to any local account with read access. The fix explicitly sets `os.Chmod(filepath, 0600)` immediately after database creation, ensuring only the owner can access authentication secrets.

high

undici 8.10.0 Cache Poisoning: CVE-2026-85152 Auth Bypass

undici, the HTTP client used by Node.js's `fetch()` implementation, shipped a caching layer that did not properly isolate cached responses by origin, letting a response poisoned on one origin be served to requests for another. This created a path to cross-origin authentication bypass, tracked as CVE-2026-85152 and fixed in undici 8.10.2.

critical

Hybridauth Telegram OAuth Timing Attack in `authenticateCheckError()`

The Telegram OAuth provider in Hybridauth relied on `strcmp()` to validate HMAC-SHA256 signatures, exposing a timing side-channel that could allow attackers to forge authentication tokens character by character. The fix replaces this with PHP's timing-safe `hash_equals()` function, eliminating the information leak.

critical

Node.js Auth Query SQL Injection via Group Code Interpolation

A sign-in authorization check built its SQL query by mapping an `authorizedGroups` array into quoted string literals and joining them directly into a template literal, creating a classic SQL injection point in a critical authentication path. The fix replaces every interpolated value — including the previously "typed" parameter — with `?` placeholders bound through a value-builder helper, closing off the injection vector entirely.

high

PHP session_start() Missing Secure Cookie Flags in lasturl.php

The `session_start()` call in the last URL tracking component failed to set secure session cookie parameters, allowing session hijacking via man-in-the-middle attacks on HTTP connections or XSS exploitation. The fix configures `session_set_cookie_params()` with `httponly`, `secure`, and `samesite` attributes before starting the session.