Flask Hashids with Secret Key as Salt

MEDIUM

Detects Hashids initialized with app.secret_key as the salt, which exposes Flask's secret key because the Hashids salt is recoverable through cryptanalysis.

Rule Information

Language
Python
Category
Flask
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythonflaskhashidssecret-keycryptographysaltsession-forgeryCWE-327OWASP-A02
CWE References

Interactive Playground

Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.

pathfinder scan --ruleset python/PYTHON-FLASK-SEC-018 --project .
1
2
3
4
5
6
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

About This Rule

Understanding the vulnerability and how it is detected

This rule detects Flask applications that initialize Hashids with Flask's secret key as the salt: Hashids(salt=app.secret_key). Hashids is a library for encoding integers into short URL-friendly strings. It is not a cryptographic hash function -- it is a bidirectional encoding scheme, and the salt is recoverable.

In 2015, security researcher Phil Carnage published a cryptanalysis showing that the Hashids salt can be recovered with approximately 20 known plaintext pairs (input integer and corresponding hashid). This means an attacker who observes enough hashids in your application's URLs can reverse-engineer the salt value. If the salt is Flask's secret_key, the attacker now has the key used to sign session cookies, CSRF tokens, and any other Flask security primitive that depends on SECRET_KEY. With the secret key, the attacker can forge arbitrary session cookies and authenticate as any user, including administrators.

The detection uses HashidsModule.method("Hashids").where("salt", "app.secret_key") -- a QueryType-based precise match. HashidsModule declares fqns=["hashids"], so only Hashids objects imported from the hashids package are matched. The .where("salt", "app.secret_key") filter means only calls where the salt keyword argument is specifically the Flask secret key attribute are flagged. Using a separate, dedicated salt value for Hashids does not trigger this rule. This precision delivers zero false positives on correctly configured applications.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Flask Secret Key Exposure via Hashids Cryptanalysis

With approximately 20 known (integer, hashid) pairs -- easily obtained from any paginated list, user profile URL, or content ID that exposes hashids -- an attacker can recover the Hashids salt through known-plaintext cryptanalysis. If that salt is app.secret_key, the attacker has the key used to sign everything Flask protects with it.

2

Session Cookie Forgery

Flask's session cookie is signed with SECRET_KEY using itsdangerous. Once an attacker obtains the secret key, they can forge a session cookie with any user ID -- including admin accounts -- without ever knowing a password or exploiting authentication logic.

3

CSRF Token Bypass

Flask-WTF's CSRF tokens are also derived from SECRET_KEY. With the key recovered, an attacker can generate valid CSRF tokens for any target user's session, bypassing CSRF protection on all state-changing endpoints.

4

Itsdangerous Token Forgery

Any itsdangerous-based token (password reset links, email confirmation tokens, timed tokens) that the application generates using SECRET_KEY can be forged by an attacker who possesses the key.

How to Fix

Recommended remediation steps

  • 1Use a separate, independently generated value for the Hashids salt -- for example, an environment variable HASHIDS_SALT that is distinct from FLASK_SECRET_KEY.
  • 2Rotate the Hashids salt independently of the Flask secret key so a compromise of one does not affect the other.
  • 3Understand that Hashids is not a security primitive -- it is an obfuscation library. Do not rely on Hashids to protect access control. Always enforce authorization server-side regardless of whether the ID is encoded.
  • 4If you need cryptographically secure token generation for URLs, use itsdangerous.URLSafeTimedSerializer with a dedicated signing key rather than Hashids.
  • 5Store sensitive key material (secret keys, salts) in a secrets manager or environment variables, never hardcoded in source code.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

This rule uses a QueryType-based precise match: HashidsModule.method("Hashids").where("salt", "app.secret_key"). HashidsModule declares fqns=["hashids"], restricting matches to objects imported from the hashids package. The .method("Hashids") step targets the Hashids() constructor call. The .where("salt", "app.secret_key") step filters to calls where the salt keyword argument is specifically the attribute access app.secret_key -- the Flask application's secret key. Using a variable that happens to equal the secret key at runtime, or using app.config["SECRET_KEY"] instead of app.secret_key, are separate patterns not covered by this precise match. The rule operates at the call-site level without cross-file dataflow analysis.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

OWASP Top 10
A02:2021 - Cryptographic Failures: use strong, purpose-specific keys for each cryptographic function
CWE Top 25
CWE-327 Use of a Broken or Risky Cryptographic Algorithm
PCI DSS v4.0
Requirement 3.5 -- protect cryptographic keys and prevent key reuse across different security functions
NIST SP 800-53
SC-12: Cryptographic Key Establishment and Management -- use separate keys for separate functions

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Flask Hashids with Secret Key as Salt

Hashids uses the salt in a way that is mathematically reversible given enough known plaintext pairs. The 2015 cryptanalysis by Phil Carnage demonstrated recovery with approximately 20 (input, output) pairs. In a web application that exposes encoded IDs in URLs or API responses, an attacker can easily collect these pairs by incrementing input integers and observing the outputs.
The .where("salt", "app.secret_key") constraint makes this a zero-false-positive rule. It only flags the specific case where the salt argument is Flask's secret key attribute. Hashids with any other salt value is not flagged, even if that other value might also be sensitive. The precise match means every finding is a confirmed misuse of the secret key, not a general Hashids audit.
No. Hashids is explicitly documented as an obfuscation library, not a cryptographic one. The author warns against using it for security purposes. It encodes integers into short strings in a way that is aesthetically pleasing and reversible. It provides no security guarantees against an adversary who can make observations.
For URL-safe tokens that need to be tamper-evident, use itsdangerous.URLSafeTimedSerializer or URLSafeSerializer with a dedicated signing key. For simple obfuscation without security requirements, use a UUID or a different encoding scheme. For access-controlled resources, enforce authorization server-side -- the token format does not substitute for access control.
Run: pathfinder ci --ruleset python/flask/PYTHON-FLASK-SEC-018 --project . The rule outputs SARIF, JSON, or CSV and can post inline pull request comments on GitHub.
No. This rule matches the specific attribute access pattern app.secret_key. Using app.config["SECRET_KEY"] as the salt is an equivalent vulnerability but a different code pattern. Review your codebase for both forms when addressing this finding.

New feature

Get these findings posted directly on your GitHub pull requests

The Flask Hashids with Secret Key as Salt rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works