VNX-344: Use of Invariant Value in Dynamically Changing Context
A Content-Security-Policy nonce or an OAuth state parameter that must be fresh per response or per login is written into the source as a constant, in Go, Java, JavaScript/TypeScript, Python, PHP and HTML.
| Field | Value |
|---|---|
| Rule ID | VNX-344 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | Go, Java, JavaScript / TypeScript, Python, PHP, HTML |
| CWE | CWE-344 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | invariant-value cwe-344 csp oauth |
| Source | internal/sast/rules/vnx-344.rego |
Overview
VNX-344 maps to CWE-344: Use of Invariant Value in Dynamically Changing Context. It reports values whose security depends on being different every time, written as constants:
- a Content-Security-Policy nonce source with a literal value (
'nonce-abc123') in a header string, a helmet/CSP directive list or an HTML<meta>policy, and a<script>/<style>nonceattribute with a literal value; - an OAuth authorisation request whose
stateis a constant: a literalstate=valuein an authorize URL (one that carriesresponse_type=orredirect_uri=, or whose path is/authorize),authorization_url()/authorize_redirect()/create_authorization_url()givenstate="literal", Gooauth2.Config.AuthCodeURL("literal"), league/oauth2-clientgetAuthorizationUrl(['state' => 'literal']), and in JavaScript/TypeScript astate: 'literal'option given to simple-oauth2authorizeURL(), openid-clientauthorizationUrl()orpassport.authenticate()(passport’sstate: trueis not reported).
Why This Matters
A CSP nonce only works because an attacker cannot guess it. A fixed nonce is visible to anyone who loads the page once, so injected script that carries it runs and the policy protects nothing. A fixed OAuth state gives the callback no way to tell a login this browser started from one an attacker forced on it (login CSRF).
Not reported: nonces and states interpolated from a variable (f-strings, template literals, %s, concatenation, {{ }} and <%= %> templates, JSX attributes), placeholders substituted at runtime ('nonce-__NONCE__'), REST filters such as ?client_id=5&state=open, comments, test code, and identifiers that merely contain the words. Constant IVs, nonces and salts passed to cipher or KDF calls are VNX-CRYPTO-010’s concern; hard-coded secrets belong to the secrets rules.
What Gets Flagged
w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'nonce-Zm9vYmFyMTIz'")
<script nonce="2726c7f26c" src="/main.js"></script>
url, _ = oauth.authorization_url("https://idp.example.com/authorize", state="xyz123")
url := googleConf.AuthCodeURL("random-state-string", oauth2.AccessTypeOffline)
Remediation
- Generate a CSP nonce per response from a cryptographic generator (
secrets.token_urlsafe(16),crypto.randomBytes(16),crypto/rand) and put the same value in the header and in the page’snonceattributes. - Generate the OAuth
stateper authorisation request, store it in the session, and compare it on the callback before exchanging the code.
References
- CWE-344: Use of Invariant Value in Dynamically Changing Context
- MDN: CSP nonce
- RFC 6749 section 10.12: Cross-Site Request Forgery
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-344
// nosec vnx-344
Or record an ignore rule: vulnetix ignore add --rule vnx-344 --reason "...". See Suppressing findings.