@greenarmor/ges-audit-engine 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -10,7 +10,11 @@ const IGNORE_DIRS = new Set([
|
|
|
10
10
|
"node_modules", ".git", "dist", "build", ".next", ".nuxt", "coverage",
|
|
11
11
|
".ges", "vendor", "__pycache__", ".venv", "venv", ".turbo", ".cache",
|
|
12
12
|
"reports", "compliance", "security", "controls", "policies", "checklists", "docs",
|
|
13
|
+
"bundle", ".crush", ".vscode", ".idea",
|
|
13
14
|
]);
|
|
15
|
+
const SKIP_PATHS = [
|
|
16
|
+
"/audit-engine/src/",
|
|
17
|
+
];
|
|
14
18
|
const IGNORE_EXTENSIONS = new Set([
|
|
15
19
|
".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".ico", ".woff",
|
|
16
20
|
".woff2", ".ttf", ".eot", ".mp4", ".mp3", ".zip", ".gz", ".tar",
|
|
@@ -30,7 +34,10 @@ function collectFiles(root) {
|
|
|
30
34
|
else if (entry.isFile()) {
|
|
31
35
|
const ext = path.extname(entry.name);
|
|
32
36
|
if (!IGNORE_EXTENSIONS.has(ext)) {
|
|
33
|
-
|
|
37
|
+
const rel = path.relative(root, fullPath).replace(/\\/g, "/");
|
|
38
|
+
if (!SKIP_PATHS.some(skip => rel.includes(skip))) {
|
|
39
|
+
files.push(rel);
|
|
40
|
+
}
|
|
34
41
|
}
|
|
35
42
|
}
|
|
36
43
|
}
|
|
@@ -56,10 +63,85 @@ function readFiles(root, files) {
|
|
|
56
63
|
}
|
|
57
64
|
return contents;
|
|
58
65
|
}
|
|
66
|
+
function detectWebProject(fileContents) {
|
|
67
|
+
const webPatterns = [
|
|
68
|
+
/from\s+['"]express['"]/, /require\s*\(\s*['"]express['"]\s*\)/,
|
|
69
|
+
/from\s+['"]fastify['"]/, /require\s*\(\s*['"]fastify['"]\s*\)/,
|
|
70
|
+
/from\s+['"]koa['"]/, /require\s*\(\s*['"]koa['"]\s*\)/,
|
|
71
|
+
/from\s+['"]hono['"]/, /require\s*\(\s*['"]hono['"]\s*\)/,
|
|
72
|
+
/from\s+['"]@nestjs/, /from\s+['"]next['"]/, /from\s+['"]nuxt['"]/,
|
|
73
|
+
/from\s+['"]@sveltejs/, /from\s+['"]@remix-run/,
|
|
74
|
+
/from\s+['"]@angular/, /from\s+['"]vue['"]/,
|
|
75
|
+
/import\s+django/, /from\s+flask\s+import/, /from\s+fastapi\s+import/,
|
|
76
|
+
/from\s+sanic\s+import/, /from\s+aiohttp\s+import/, /import\s+tornado/,
|
|
77
|
+
/use\s+gin\.Default\(\)|gin\.New\(\)/, /fiber\.New\(\)/,
|
|
78
|
+
/echo\.New\(\)/, /mux\.NewRouter\(\)/, /chi\.NewRouter\(\)/,
|
|
79
|
+
/iris\.New\(\)/,
|
|
80
|
+
/use\s+Actix\s*Web/, /use\s+rocket/, /use\s+warp/, /use\s+axum/,
|
|
81
|
+
/Rails\.application/, /ActionController::Base/, /Sinatra::Base/,
|
|
82
|
+
/import\s+io\.express/, /import\s+io\.ktor/, /import\s+spark\.Spark/,
|
|
83
|
+
/@SpringBootApplication/, /@Controller/, /@RestController/,
|
|
84
|
+
/use\s+Rocketeer/, /Route::get|Route::post/, /use\s+Illuminate/,
|
|
85
|
+
/using\s+Microsoft\.AspNetCore/, /using\s+Nancy/, /ControllerBase/,
|
|
86
|
+
/createServer\s*\(\s*.*request\b/, /http\.createServer/,
|
|
87
|
+
/router\.(get|post|put|delete|patch)\s*\(/,
|
|
88
|
+
];
|
|
89
|
+
for (const [, content] of fileContents) {
|
|
90
|
+
for (const pattern of webPatterns) {
|
|
91
|
+
if (pattern.test(content))
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const [filePath, content] of fileContents) {
|
|
96
|
+
if (filePath === "package.json") {
|
|
97
|
+
try {
|
|
98
|
+
const pkg = JSON.parse(content);
|
|
99
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
100
|
+
if (allDeps.express || allDeps.fastify || allDeps.koa || allDeps.hono ||
|
|
101
|
+
allDeps.next || allDeps.nuxt || allDeps["@nestjs/core"] || allDeps["@sveltejs/kit"] ||
|
|
102
|
+
allDeps["@remix-run/node"] || allDeps["@angular/core"] || allDeps.vue) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch { /* not json */ }
|
|
107
|
+
}
|
|
108
|
+
if (filePath === "requirements.txt" || filePath === "pyproject.toml") {
|
|
109
|
+
if (/django|flask|fastapi|sanic|aiohttp|tornado|starlette/i.test(content))
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
if (filePath === "go.mod") {
|
|
113
|
+
if (/gin-gonic|fiber|echo|chi|gorilla\/mux|iris/i.test(content))
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
if (filePath === "Cargo.toml") {
|
|
117
|
+
if (/actix-web|rocket|warp|axum|tide/i.test(content))
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
if (filePath === "Gemfile") {
|
|
121
|
+
if (/rails|sinatra|hanami/i.test(content))
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
if (filePath === "pom.xml" || filePath === "build.gradle") {
|
|
125
|
+
if (/spring-boot|ktor|sparkjava|quarkus/i.test(content))
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
if (filePath === "composer.json") {
|
|
129
|
+
try {
|
|
130
|
+
const pkg = JSON.parse(content);
|
|
131
|
+
const allDeps = { ...pkg.require, ...pkg["require-dev"] };
|
|
132
|
+
if (allDeps["laravel/framework"] || allDeps["symfony/symfony"] || allDeps["slim/slim"])
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
catch { /* not json */ }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
59
140
|
export function runAudit(root) {
|
|
60
141
|
const files = collectFiles(root);
|
|
61
142
|
const fileContents = readFiles(root, files);
|
|
62
|
-
const
|
|
143
|
+
const isWebProject = detectWebProject(fileContents);
|
|
144
|
+
const ctx = { root, files, fileContents, isWebProject };
|
|
63
145
|
const scanners = [
|
|
64
146
|
new SecretsScanner(),
|
|
65
147
|
new CryptoScanner(),
|
|
@@ -4,6 +4,8 @@ export class AuthScanner {
|
|
|
4
4
|
scan(ctx) {
|
|
5
5
|
const findings = [];
|
|
6
6
|
const content = ctx.fileContents;
|
|
7
|
+
if (!ctx.isWebProject)
|
|
8
|
+
return findings;
|
|
7
9
|
const hasAuthMiddleware = this.detectAuthMiddleware(content);
|
|
8
10
|
const routesWithoutAuth = this.detectRoutesWithoutAuth(content, hasAuthMiddleware);
|
|
9
11
|
const hasRateLimiting = this.detectRateLimiting(content);
|
|
@@ -43,7 +43,7 @@ export class ConfigScanner {
|
|
|
43
43
|
fix: "npm install cors and configure allowed origins explicitly.",
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
|
-
const auditDeps = ["
|
|
46
|
+
const auditDeps = ["lodash", "axios", "underscore"];
|
|
47
47
|
for (const dep of auditDeps) {
|
|
48
48
|
if (deps[dep]) {
|
|
49
49
|
findings.push({
|
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
const DB_SCHEMA_EXTENSIONS = new Set([
|
|
2
|
+
".prisma", ".sql",
|
|
3
|
+
]);
|
|
4
|
+
const DB_SCHEMA_FILENAMES = [
|
|
5
|
+
/(?:^|[\/\\])(?:schema|migration|knexfile|drizzle\.config|database\.conf)/i,
|
|
6
|
+
];
|
|
7
|
+
const DB_DIR_INDICATORS = [
|
|
8
|
+
/[\/\\](?:migrations?|models?|entities?|repositories?|schemas?|db|database)[\/\\]/i,
|
|
9
|
+
];
|
|
10
|
+
const ORM_ENTITY_PATTERNS = {
|
|
11
|
+
".ts": /(?:@Entity|@Table|@Schema|BaseModel)\s*\(|(?:Model|Entity|Schema)\s+extends\s+|Schema\s*=\s*new\s+mongoose\.Schema/i,
|
|
12
|
+
".js": /(?:@Entity|@Table|@Schema|BaseModel)\s*\(/,
|
|
13
|
+
".py": /class\s+\w+\s*\(\s*(?:models\.Model|Base|declarative_base)\)/i,
|
|
14
|
+
".rb": /class\s+\w+\s*<\s*(?:ApplicationRecord|ActiveRecord::Base)/i,
|
|
15
|
+
".go": /type\s+\w+\s+struct\s*\{[\s\S]*?gorm/i,
|
|
16
|
+
".java": /@Entity\s*(?:public\s+)?class/i,
|
|
17
|
+
".php": /class\s+\w+\s+extends\s+(?:Model|Eloquent|Doctrine)/i,
|
|
18
|
+
};
|
|
19
|
+
function isDatabaseSchemaFile(filePath, content) {
|
|
20
|
+
const ext = filePath.substring(filePath.lastIndexOf("."));
|
|
21
|
+
const basename = filePath.substring(filePath.lastIndexOf("/") + 1);
|
|
22
|
+
if (DB_SCHEMA_EXTENSIONS.has(ext))
|
|
23
|
+
return true;
|
|
24
|
+
for (const pattern of DB_SCHEMA_FILENAMES) {
|
|
25
|
+
if (pattern.test(basename))
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
for (const pattern of DB_DIR_INDICATORS) {
|
|
29
|
+
if (pattern.test(filePath))
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
const ormPattern = ORM_ENTITY_PATTERNS[ext];
|
|
33
|
+
if (ormPattern && ormPattern.test(content))
|
|
34
|
+
return true;
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
1
37
|
export class DatabaseScanner {
|
|
2
38
|
name = "database";
|
|
3
39
|
scan(ctx) {
|
|
@@ -7,56 +43,55 @@ export class DatabaseScanner {
|
|
|
7
43
|
return findings;
|
|
8
44
|
}
|
|
9
45
|
checkSchemaPatterns(ctx, findings) {
|
|
10
|
-
const requiredAuditColumns = ["created_at", "updated_at"];
|
|
11
|
-
const recommendedAuditColumns = ["deleted_at", "created_by", "updated_by"];
|
|
12
|
-
const codeExtensions = new Set([".ts", ".tsx", ".js", ".jsx", ".prisma", ".sql"]);
|
|
13
46
|
for (const [filePath, content] of ctx.fileContents) {
|
|
14
|
-
|
|
15
|
-
if (!codeExtensions.has(ext))
|
|
47
|
+
if (!isDatabaseSchemaFile(filePath, content))
|
|
16
48
|
continue;
|
|
17
|
-
const hasTimestamps = /\b(?:timestamps|created_at|createdAt)\s*[:\(]/i.test(content);
|
|
18
|
-
const hasSoftDelete = /\b(?:deleted_at|deletedAt|softDelete|paranoid)\s*[:\(]/i.test(content);
|
|
19
|
-
const hasUserAudit = /\b(?:created_by|createdBy|updated_by|updatedBy)\s*[:\(]/i.test(content);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
const hasTimestamps = /\b(?:timestamps|created_at|createdAt|createdDate|date_created|timecreated|createdTime)\s*[:\(]/i.test(content);
|
|
50
|
+
const hasSoftDelete = /\b(?:deleted_at|deletedAt|softDelete|paranoid|is_deleted|isDeleted|deleted|active)\s*[:\(]/i.test(content);
|
|
51
|
+
const hasUserAudit = /\b(?:created_by|createdBy|updated_by|updatedBy|owner_id|author_id)\s*[:\(]/i.test(content);
|
|
52
|
+
const hasSchemaDef = /\b(?:model|schema|entity|table|struct|class)\b.*\{/i.test(content) ||
|
|
53
|
+
/\bCREATE\s+TABLE\b/i.test(content) ||
|
|
54
|
+
/@(?:Entity|Table|Schema)\b/.test(content);
|
|
55
|
+
if (!hasSchemaDef)
|
|
56
|
+
continue;
|
|
57
|
+
if (!hasTimestamps) {
|
|
58
|
+
findings.push({
|
|
59
|
+
ruleId: "DB-001",
|
|
60
|
+
severity: "high",
|
|
61
|
+
category: "database",
|
|
62
|
+
title: "Missing audit timestamps in schema",
|
|
63
|
+
description: "Database schema does not include created_at/updated_at timestamps. These are mandatory for audit trails.",
|
|
64
|
+
file: filePath,
|
|
65
|
+
evidence: "No created_at/updated_at columns detected",
|
|
66
|
+
controlIds: ["GDPR-ART32-006"],
|
|
67
|
+
fix: "Add created_at and updated_at columns. In Prisma: add DateTime fields, in Sequelize: timestamps: true, in Django: auto_now_add=True.",
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (!hasSoftDelete) {
|
|
71
|
+
findings.push({
|
|
72
|
+
ruleId: "DB-002",
|
|
73
|
+
severity: "medium",
|
|
74
|
+
category: "database",
|
|
75
|
+
title: "Missing soft delete pattern",
|
|
76
|
+
description: "No deleted_at column or soft delete pattern found. Hard deletes prevent audit trail and data recovery.",
|
|
77
|
+
file: filePath,
|
|
78
|
+
evidence: "No deleted_at/softDelete pattern detected",
|
|
79
|
+
controlIds: ["GDPR-ART32-007"],
|
|
80
|
+
fix: "Add deleted_at column or soft delete flag. In Prisma: DeletedAt DateTime?, in Sequelize: paranoid: true, in Django: SoftDeleteModel.",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (!hasUserAudit) {
|
|
84
|
+
findings.push({
|
|
85
|
+
ruleId: "DB-003",
|
|
86
|
+
severity: "medium",
|
|
87
|
+
category: "database",
|
|
88
|
+
title: "Missing user audit columns",
|
|
89
|
+
description: "No created_by/updated_by columns found. Track who makes changes for accountability.",
|
|
90
|
+
file: filePath,
|
|
91
|
+
evidence: "No created_by/updated_by columns detected",
|
|
92
|
+
controlIds: ["GDPR-ART32-006"],
|
|
93
|
+
fix: "Add created_by and updated_by columns to track which user made changes.",
|
|
94
|
+
});
|
|
60
95
|
}
|
|
61
96
|
}
|
|
62
97
|
}
|
package/dist/scanners/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@greenarmor/ges-audit-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "GESF Audit Engine - Audit trails and compliance evaluation",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@greenarmor/ges-core": "0.
|
|
15
|
+
"@greenarmor/ges-core": "0.5.1"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"typescript": "^6.0.0",
|