Skip to content

Commit 1378150

Browse files
committed
feat: initial codeql setup
1 parent e580f64 commit 1378150

File tree

8 files changed

+500
-0
lines changed

8 files changed

+500
-0
lines changed

.github/codeql/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# CodeQL Security Analysis for Unraid API
2+
3+
This directory contains custom CodeQL queries and configurations for security analysis of the Unraid API codebase.
4+
5+
## Overview
6+
7+
The analysis is configured to run:
8+
- On all pushes to the main branch
9+
- On all pull requests
10+
- Weekly via scheduled runs
11+
12+
## Custom Queries
13+
14+
The following custom queries are implemented:
15+
16+
1. **API Authorization Bypass Detection**
17+
Identifies API handlers that may not properly check authorization before performing operations.
18+
19+
2. **GraphQL Injection Detection**
20+
Detects potential injection vulnerabilities in GraphQL queries and operations.
21+
22+
3. **Hardcoded Secrets Detection**
23+
Finds potential hardcoded secrets or credentials in the codebase.
24+
25+
4. **Insecure Cryptographic Implementations**
26+
Identifies usage of weak cryptographic algorithms or insecure random number generation.
27+
28+
5. **Path Traversal Vulnerability Detection**
29+
Detects potential path traversal vulnerabilities in file system operations.
30+
31+
## Configuration
32+
33+
The CodeQL analysis is configured in:
34+
- `.github/workflows/codeql-analysis.yml` - Workflow configuration
35+
- `.github/codeql/codeql-config.yml` - CodeQL engine configuration
36+
37+
## Running Locally
38+
39+
To run these queries locally:
40+
41+
1. Install the CodeQL CLI: https://github.com/github/codeql-cli-binaries/releases
42+
2. Create a CodeQL database:
43+
```
44+
codeql database create <db-name> --language=javascript --source-root=.
45+
```
46+
3. Run a query:
47+
```
48+
codeql query run .github/codeql/custom-queries/javascript/api-auth-bypass.ql --database=<db-name>
49+
```

.github/codeql/codeql-config.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: "Unraid API CodeQL Configuration"
2+
3+
disable-default-queries: false
4+
5+
queries:
6+
- name: Extended Security Queries
7+
uses: security-extended
8+
- name: Custom Unraid API Queries
9+
uses: ./.github/codeql/custom-queries
10+
11+
query-filters:
12+
- exclude:
13+
problem.severity:
14+
- warning
15+
- recommendation
16+
tags contain: security
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* @name Potential API Authorization Bypass
3+
* @description Functions that process API requests without verifying authorization may lead to security vulnerabilities.
4+
* @kind problem
5+
* @problem.severity error
6+
* @precision medium
7+
* @id js/api-auth-bypass
8+
* @tags security
9+
* external/cwe/cwe-285
10+
*/
11+
12+
import javascript
13+
14+
/**
15+
* Identifies functions that appear to handle API requests
16+
*/
17+
predicate isApiHandler(Function f) {
18+
exists(f.getAParameter()) and
19+
(
20+
f.getName().regexpMatch("(?i).*(api|handler|controller|resolver|endpoint).*") or
21+
exists(CallExpr call |
22+
call.getCalleeName().regexpMatch("(?i).*(get|post|put|delete|patch).*") and
23+
call.getArgument(1) = f
24+
)
25+
)
26+
}
27+
28+
/**
29+
* Identifies expressions that appear to perform authorization checks
30+
*/
31+
predicate isAuthCheck(DataFlow::Node node) {
32+
exists(CallExpr call |
33+
call.getCalleeName().regexpMatch("(?i).*(authorize|authenticate|isAuth|checkAuth|verifyAuth|hasPermission|isAdmin|canAccess).*") and
34+
call.flow().getASuccessor*() = node
35+
)
36+
}
37+
38+
from Function apiHandler
39+
where
40+
isApiHandler(apiHandler) and
41+
not exists(DataFlow::Node authCheck |
42+
isAuthCheck(authCheck) and
43+
authCheck.getEnclosingExpr().getEnclosingFunction() = apiHandler
44+
)
45+
select apiHandler, "API handler function may not perform proper authorization checks."
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* @name Potential GraphQL Injection
3+
* @description User-controlled input used directly in GraphQL queries may lead to injection vulnerabilities.
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @precision high
7+
* @id js/graphql-injection
8+
* @tags security
9+
* external/cwe/cwe-943
10+
*/
11+
12+
import javascript
13+
import DataFlow::PathGraph
14+
15+
class GraphQLQueryExecution extends DataFlow::CallNode {
16+
GraphQLQueryExecution() {
17+
exists(string name |
18+
name = this.getCalleeName() and
19+
(
20+
name = "execute" or
21+
name = "executeQuery" or
22+
name = "query" or
23+
name.regexpMatch("(?i).*graphql.*query.*")
24+
)
25+
)
26+
}
27+
28+
DataFlow::Node getQuery() {
29+
result = this.getArgument(0)
30+
}
31+
}
32+
33+
class UserControlledInput extends DataFlow::Node {
34+
UserControlledInput() {
35+
exists(DataFlow::ParameterNode param |
36+
param.getName().regexpMatch("(?i).*(query|request|input|args|variables|params).*") and
37+
this = param
38+
)
39+
or
40+
exists(DataFlow::PropRead prop |
41+
prop.getPropertyName().regexpMatch("(?i).*(query|request|input|args|variables|params).*") and
42+
this = prop
43+
)
44+
}
45+
}
46+
47+
/**
48+
* Holds if `node` is a string concatenation.
49+
*/
50+
predicate isStringConcatenation(DataFlow::Node node) {
51+
exists(BinaryExpr concat |
52+
concat.getOperator() = "+" and
53+
concat.flow() = node
54+
)
55+
}
56+
57+
class GraphQLInjectionConfig extends TaintTracking::Configuration {
58+
GraphQLInjectionConfig() { this = "GraphQLInjectionConfig" }
59+
60+
override predicate isSource(DataFlow::Node source) {
61+
source instanceof UserControlledInput
62+
}
63+
64+
override predicate isSink(DataFlow::Node sink) {
65+
exists(GraphQLQueryExecution exec | sink = exec.getQuery())
66+
}
67+
68+
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
69+
// Add any GraphQL-specific taint steps if needed
70+
isStringConcatenation(succ) and
71+
succ.(DataFlow::BinaryExprNode).getAnOperand() = pred
72+
}
73+
}
74+
75+
from GraphQLInjectionConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
76+
where config.hasFlowPath(source, sink)
77+
select sink.getNode(), source, sink, "GraphQL query may contain user-controlled input from $@.", source.getNode(), "user input"
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* @name Hardcoded Secrets
3+
* @description Hardcoded secrets or credentials in source code can lead to security vulnerabilities.
4+
* @kind problem
5+
* @problem.severity error
6+
* @precision medium
7+
* @id js/hardcoded-secrets
8+
* @tags security
9+
* external/cwe/cwe-798
10+
*/
11+
12+
import javascript
13+
14+
/**
15+
* Identifies variable declarations or assignments that may contain secrets
16+
*/
17+
predicate isSensitiveAssignment(DataFlow::Node node) {
18+
exists(DataFlow::PropWrite propWrite |
19+
propWrite.getPropertyName().regexpMatch("(?i).*(secret|key|password|token|credential|auth).*") and
20+
propWrite.getRhs() = node
21+
)
22+
or
23+
exists(VariableDeclarator decl |
24+
decl.getName().regexpMatch("(?i).*(secret|key|password|token|credential|auth).*") and
25+
decl.getInit().flow() = node
26+
)
27+
}
28+
29+
/**
30+
* Identifies literals that look like secrets
31+
*/
32+
predicate isSecretLiteral(StringLiteral literal) {
33+
// Match alphanumeric strings of moderate length that may be secrets
34+
literal.getValue().regexpMatch("[A-Za-z0-9_\\-]{8,}") and
35+
36+
not (
37+
// Skip likely non-sensitive literals
38+
literal.getValue().regexpMatch("(?i)^(true|false|null|undefined|localhost|development|production|staging)$") or
39+
// Skip URLs without credentials
40+
literal.getValue().regexpMatch("^https?://[^:@/]+")
41+
)
42+
}
43+
44+
from DataFlow::Node source
45+
where
46+
isSensitiveAssignment(source) and
47+
(
48+
exists(StringLiteral literal |
49+
literal.flow() = source and
50+
isSecretLiteral(literal)
51+
)
52+
)
53+
select source, "This assignment may contain a hardcoded secret or credential."
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* @name Insecure Cryptographic Implementation
3+
* @description Usage of weak cryptographic algorithms or improper implementations can lead to security vulnerabilities.
4+
* @kind problem
5+
* @problem.severity error
6+
* @precision high
7+
* @id js/insecure-crypto
8+
* @tags security
9+
* external/cwe/cwe-327
10+
*/
11+
12+
import javascript
13+
14+
/**
15+
* Identifies calls to crypto functions with insecure algorithms
16+
*/
17+
predicate isInsecureCryptoCall(CallExpr call) {
18+
// Node.js crypto module uses
19+
exists(string methodName |
20+
methodName = call.getCalleeName() and
21+
(
22+
// Detect MD5 usage
23+
methodName.regexpMatch("(?i).*md5.*") or
24+
methodName.regexpMatch("(?i).*sha1.*") or
25+
26+
// Insecure crypto constructors
27+
(
28+
methodName = "createHash" or
29+
methodName = "createCipheriv" or
30+
methodName = "createDecipher"
31+
) and
32+
(
33+
exists(StringLiteral algo |
34+
algo = call.getArgument(0) and
35+
(
36+
algo.getValue().regexpMatch("(?i).*(md5|md4|md2|sha1|des|rc4|blowfish).*") or
37+
algo.getValue().regexpMatch("(?i).*(ecb).*") // ECB mode
38+
)
39+
)
40+
)
41+
)
42+
)
43+
or
44+
// Browser crypto API uses
45+
exists(MethodCallExpr mce, string propertyName |
46+
propertyName = mce.getMethodName() and
47+
(
48+
propertyName = "subtle" and
49+
exists(MethodCallExpr subtleCall |
50+
subtleCall.getReceiver() = mce and
51+
subtleCall.getMethodName() = "encrypt" and
52+
exists(ObjectExpr obj |
53+
obj = subtleCall.getArgument(0) and
54+
exists(Property p |
55+
p = obj.getAProperty() and
56+
p.getName() = "name" and
57+
exists(StringLiteral algo |
58+
algo = p.getInit() and
59+
algo.getValue().regexpMatch("(?i).*(rc4|des|aes-cbc).*")
60+
)
61+
)
62+
)
63+
)
64+
)
65+
)
66+
}
67+
68+
/**
69+
* Identifies usage of Math.random() for security-sensitive operations
70+
*/
71+
predicate isInsecureRandomCall(CallExpr call) {
72+
exists(PropertyAccess prop |
73+
prop.getPropertyName() = "random" and
74+
prop.getBase().toString() = "Math" and
75+
call.getCallee() = prop
76+
)
77+
}
78+
79+
from Expr insecureExpr, string message
80+
where
81+
(
82+
insecureExpr instanceof CallExpr and
83+
isInsecureCryptoCall(insecureExpr) and
84+
message = "Using potentially insecure cryptographic algorithm or mode."
85+
) or (
86+
insecureExpr instanceof CallExpr and
87+
isInsecureRandomCall(insecureExpr) and
88+
message = "Using Math.random() for security-sensitive operation. Consider using crypto.getRandomValues() instead."
89+
)
90+
select insecureExpr, message

0 commit comments

Comments
 (0)