mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-05 11:26:33 +00:00
add v1 secret scanning
This commit is contained in:
+23
-5
@@ -1,6 +1,9 @@
|
|||||||
{
|
{
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"plugins": ["@typescript-eslint", "unused-imports"],
|
"plugins": [
|
||||||
|
"@typescript-eslint",
|
||||||
|
"unused-imports"
|
||||||
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:@typescript-eslint/eslint-recommended",
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
@@ -8,14 +11,29 @@
|
|||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-console": 2,
|
"no-console": 2,
|
||||||
"quotes": ["error", "double", { "avoidEscape": true }],
|
"quotes": [
|
||||||
"comma-dangle": ["error", "only-multiline"],
|
"error",
|
||||||
|
"double",
|
||||||
|
{
|
||||||
|
"avoidEscape": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"comma-dangle": [
|
||||||
|
"error",
|
||||||
|
"only-multiline"
|
||||||
|
],
|
||||||
"@typescript-eslint/no-unused-vars": "off",
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
"unused-imports/no-unused-imports": "error",
|
"unused-imports/no-unused-imports": "error",
|
||||||
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
"unused-imports/no-unused-vars": [
|
"unused-imports/no-unused-vars": [
|
||||||
"warn",
|
"warn",
|
||||||
{ "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
|
{
|
||||||
|
"vars": "all",
|
||||||
|
"varsIgnorePattern": "^_",
|
||||||
|
"args": "after-used",
|
||||||
|
"argsIgnorePattern": "^_"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"sort-imports": ["error", { "ignoreDeclarationSort": true }]
|
"sort-imports": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@ import * as signupController from "./signupController";
|
|||||||
import * as userActionController from "./userActionController";
|
import * as userActionController from "./userActionController";
|
||||||
import * as userController from "./userController";
|
import * as userController from "./userController";
|
||||||
import * as workspaceController from "./workspaceController";
|
import * as workspaceController from "./workspaceController";
|
||||||
|
import * as secretScanningController from "./secretScanningController";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
authController,
|
authController,
|
||||||
@@ -30,4 +31,5 @@ export {
|
|||||||
userActionController,
|
userActionController,
|
||||||
userController,
|
userController,
|
||||||
workspaceController,
|
workspaceController,
|
||||||
|
secretScanningController
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import GitAppInstallationSession from "../../models/gitAppInstallationSession";
|
||||||
|
import crypto from "crypto";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
import { UnauthorizedRequestError } from "../../utils/errors";
|
||||||
|
import GitAppOrganizationInstallation from "../../models/gitAppOrganizationInstallation";
|
||||||
|
import { MembershipOrg } from "../../models";
|
||||||
|
import GitRisks, { STATUS_UNRESOLVED } from "../../models/gitRisks";
|
||||||
|
|
||||||
|
export const createInstallationSession = async (req: Request, res: Response) => {
|
||||||
|
const sessionId = crypto.randomBytes(16).toString("hex");
|
||||||
|
await GitAppInstallationSession.findByIdAndUpdate(
|
||||||
|
req.organization,
|
||||||
|
{
|
||||||
|
organization: new Types.ObjectId(req.organization),
|
||||||
|
sessionId: sessionId,
|
||||||
|
user: new Types.ObjectId(req.user._id)
|
||||||
|
},
|
||||||
|
{ upsert: true }
|
||||||
|
).lean();
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
sessionId: sessionId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const linkInstallationToOrganization = async (req: Request, res: Response) => {
|
||||||
|
const { installationId, sessionId } = req.body
|
||||||
|
|
||||||
|
const installationSession = await GitAppInstallationSession.findOneAndDelete({ sessionId: sessionId })
|
||||||
|
if (!installationSession) {
|
||||||
|
throw UnauthorizedRequestError()
|
||||||
|
}
|
||||||
|
|
||||||
|
const userMembership = await MembershipOrg.find({ user: req.user._id, organization: installationSession.organization })
|
||||||
|
if (!userMembership) {
|
||||||
|
throw UnauthorizedRequestError()
|
||||||
|
}
|
||||||
|
|
||||||
|
const installationLink = await GitAppOrganizationInstallation.findOneAndUpdate({
|
||||||
|
organizationId: installationSession.organization,
|
||||||
|
}, {
|
||||||
|
installationId: installationId,
|
||||||
|
organizationId: installationSession.organization,
|
||||||
|
user: installationSession.user
|
||||||
|
}, {
|
||||||
|
upsert: true
|
||||||
|
}).lean()
|
||||||
|
|
||||||
|
res.json(installationLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCurrentOrganizationInstallationStatus = async (req: Request, res: Response) => {
|
||||||
|
const { organizationId } = req.params
|
||||||
|
try {
|
||||||
|
const appInstallation = await GitAppOrganizationInstallation.findOne({ organizationId: organizationId }).lean()
|
||||||
|
if (!appInstallation) {
|
||||||
|
res.json({
|
||||||
|
appInstallationComplete: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
appInstallationComplete: true
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
res.json({
|
||||||
|
appInstallationComplete: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getRisksForOrganization = async (req: Request, res: Response) => {
|
||||||
|
const { organizationId } = req.params
|
||||||
|
const risks = await GitRisks.find({ organization: organizationId, status: STATUS_UNRESOLVED }).lean()
|
||||||
|
res.json({
|
||||||
|
risks: risks
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateRisksStatus = async (req: Request, res: Response) => {
|
||||||
|
const { riskId } = req.params
|
||||||
|
const { status } = req.body
|
||||||
|
const risks = await GitRisks.findByIdAndUpdate(riskId, {
|
||||||
|
sttaus: status
|
||||||
|
}).lean()
|
||||||
|
|
||||||
|
res.json(risks)
|
||||||
|
}
|
||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
organization as v1OrganizationRouter,
|
organization as v1OrganizationRouter,
|
||||||
password as v1PasswordRouter,
|
password as v1PasswordRouter,
|
||||||
secret as v1SecretRouter,
|
secret as v1SecretRouter,
|
||||||
|
secretScanning as v1SecretScanningRouter,
|
||||||
secretsFolder as v1SecretsFolder,
|
secretsFolder as v1SecretsFolder,
|
||||||
serviceToken as v1ServiceTokenRouter,
|
serviceToken as v1ServiceTokenRouter,
|
||||||
signup as v1SignupRouter,
|
signup as v1SignupRouter,
|
||||||
@@ -42,8 +43,8 @@ import {
|
|||||||
workspace as v1WorkspaceRouter,
|
workspace as v1WorkspaceRouter,
|
||||||
} from "./routes/v1";
|
} from "./routes/v1";
|
||||||
import {
|
import {
|
||||||
signup as v2SignupRouter,
|
|
||||||
auth as v2AuthRouter,
|
auth as v2AuthRouter,
|
||||||
|
signup as v2SignupRouter,
|
||||||
users as v2UsersRouter,
|
users as v2UsersRouter,
|
||||||
organizations as v2OrganizationsRouter,
|
organizations as v2OrganizationsRouter,
|
||||||
workspace as v2WorkspaceRouter,
|
workspace as v2WorkspaceRouter,
|
||||||
@@ -125,6 +126,7 @@ const main = async () => {
|
|||||||
app.use("/api/v1/integration", v1IntegrationRouter);
|
app.use("/api/v1/integration", v1IntegrationRouter);
|
||||||
app.use("/api/v1/integration-auth", v1IntegrationAuthRouter);
|
app.use("/api/v1/integration-auth", v1IntegrationAuthRouter);
|
||||||
app.use("/api/v1/folders", v1SecretsFolder);
|
app.use("/api/v1/folders", v1SecretsFolder);
|
||||||
|
app.use("/api/v1/secret-scanning", v1SecretScanningRouter);
|
||||||
|
|
||||||
// v2 routes (improvements)
|
// v2 routes (improvements)
|
||||||
app.use("/api/v2/signup", v2SignupRouter);
|
app.use("/api/v2/signup", v2SignupRouter);
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { Schema, Types, model } from "mongoose";
|
||||||
|
|
||||||
|
type GitAppInstallationSession = {
|
||||||
|
id: string;
|
||||||
|
sessionId: string;
|
||||||
|
organization: Types.ObjectId;
|
||||||
|
user: Types.ObjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gitAppInstallationSession = new Schema<GitAppInstallationSession>({
|
||||||
|
id: {
|
||||||
|
required: true,
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
sessionId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
organization: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
required: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "User"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const GitAppInstallationSession = model<GitAppInstallationSession>("git_app_installation_session", gitAppInstallationSession);
|
||||||
|
|
||||||
|
export default GitAppInstallationSession;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Schema, model } from "mongoose";
|
||||||
|
|
||||||
|
type Installation = {
|
||||||
|
installationId: string
|
||||||
|
organizationId: string
|
||||||
|
user: Schema.Types.ObjectId
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const gitAppOrganizationInstallation = new Schema<Installation>({
|
||||||
|
installationId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
organizationId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "User",
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const GitAppOrganizationInstallation = model<Installation>("git_app_organization_installation", gitAppOrganizationInstallation);
|
||||||
|
|
||||||
|
export default GitAppOrganizationInstallation;
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
import { Schema, model } from "mongoose";
|
||||||
|
|
||||||
|
export const STATUS_RESOLVED_FALSE_POSITIVE = "RESOLVED_FALSE_POSITIVE";
|
||||||
|
export const STATUS_RESOLVED_REVOKED = "RESOLVED_REVOKED";
|
||||||
|
export const STATUS_RESOLVED_NOT_REVOKED = "RESOLVED_NOT_REVOKED";
|
||||||
|
export const STATUS_UNRESOLVED = "UNRESOLVED";
|
||||||
|
|
||||||
|
export type GitRisks = {
|
||||||
|
id: string;
|
||||||
|
description: string;
|
||||||
|
startLine: string;
|
||||||
|
endLine: string;
|
||||||
|
startColumn: string;
|
||||||
|
endColumn: string;
|
||||||
|
match: string;
|
||||||
|
secret: string;
|
||||||
|
file: string;
|
||||||
|
symlinkFile: string;
|
||||||
|
commit: string;
|
||||||
|
entropy: string;
|
||||||
|
author: string;
|
||||||
|
email: string;
|
||||||
|
date: string;
|
||||||
|
message: string;
|
||||||
|
tags: string[];
|
||||||
|
ruleID: string;
|
||||||
|
fingerprint: string;
|
||||||
|
|
||||||
|
isFalsePositive: boolean; // New field for marking risks as false positives
|
||||||
|
isResolved: boolean; // New field for marking risks as resolved
|
||||||
|
riskOwner: string | null; // New field for setting a risk owner (nullable string)
|
||||||
|
installationId: string,
|
||||||
|
repositoryId: string,
|
||||||
|
repositoryLink: string
|
||||||
|
repositoryFullName: string
|
||||||
|
status: string
|
||||||
|
pusher: {
|
||||||
|
name: string,
|
||||||
|
email: string
|
||||||
|
},
|
||||||
|
organization: Schema.Types.ObjectId,
|
||||||
|
}
|
||||||
|
|
||||||
|
const gitRisks = new Schema<GitRisks>({
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
startLine: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
endLine: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
startColumn: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
endColumn: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
file: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
symlinkFile: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
commit: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
entropy: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
author: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
type: [String],
|
||||||
|
},
|
||||||
|
ruleID: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
fingerprint: {
|
||||||
|
type: String,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
isFalsePositive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isResolved: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
riskOwner: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
installationId: {
|
||||||
|
type: String,
|
||||||
|
require: true
|
||||||
|
},
|
||||||
|
repositoryId: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
repositoryLink: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
repositoryFullName: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
pusher: {
|
||||||
|
name: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
},
|
||||||
|
organization: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Organization",
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
STATUS_RESOLVED_FALSE_POSITIVE,
|
||||||
|
STATUS_RESOLVED_REVOKED,
|
||||||
|
STATUS_RESOLVED_NOT_REVOKED,
|
||||||
|
STATUS_UNRESOLVED
|
||||||
|
],
|
||||||
|
default: STATUS_UNRESOLVED
|
||||||
|
}
|
||||||
|
}, { timestamps: true });
|
||||||
|
|
||||||
|
const GitRisks = model<GitRisks>("GitRisks", gitRisks);
|
||||||
|
|
||||||
|
export default GitRisks;
|
||||||
@@ -15,6 +15,7 @@ import password from "./password";
|
|||||||
import integration from "./integration";
|
import integration from "./integration";
|
||||||
import integrationAuth from "./integrationAuth";
|
import integrationAuth from "./integrationAuth";
|
||||||
import secretsFolder from "./secretsFolder";
|
import secretsFolder from "./secretsFolder";
|
||||||
|
import secretScanning from "./secretScanning";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
signup,
|
signup,
|
||||||
@@ -34,4 +35,5 @@ export {
|
|||||||
integration,
|
integration,
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
secretsFolder,
|
secretsFolder,
|
||||||
|
secretScanning
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import express from "express";
|
||||||
|
const router = express.Router();
|
||||||
|
import {
|
||||||
|
requireAuth,
|
||||||
|
requireOrganizationAuth,
|
||||||
|
validateRequest,
|
||||||
|
} from "../../middleware";
|
||||||
|
import { body, param } from "express-validator";
|
||||||
|
import { createInstallationSession, getCurrentOrganizationInstallationStatus, getRisksForOrganization, linkInstallationToOrganization, updateRisksStatus } from "../../controllers/v1/secretScanningController";
|
||||||
|
import { ACCEPTED, ADMIN, MEMBER, OWNER } from "../../variables";
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/create-installation-session/organization/:organizationId",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ["jwt"],
|
||||||
|
}),
|
||||||
|
param("organizationId").exists().trim(),
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [ACCEPTED],
|
||||||
|
}),
|
||||||
|
validateRequest,
|
||||||
|
createInstallationSession
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/link-installation",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ["jwt"],
|
||||||
|
}),
|
||||||
|
body("installationId").exists().trim(),
|
||||||
|
body("sessionId").exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
linkInstallationToOrganization
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/installation-status/organization/:organizationId",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ["jwt"],
|
||||||
|
}),
|
||||||
|
param("organizationId").exists().trim(),
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [ACCEPTED],
|
||||||
|
}),
|
||||||
|
validateRequest,
|
||||||
|
getCurrentOrganizationInstallationStatus
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/organization/:organizationId/risks",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ["jwt"],
|
||||||
|
}),
|
||||||
|
param("organizationId").exists().trim(),
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [ACCEPTED],
|
||||||
|
}),
|
||||||
|
validateRequest,
|
||||||
|
getRisksForOrganization
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/organization/:organizationId/risks/:riskId/status",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ["jwt"],
|
||||||
|
}),
|
||||||
|
param("organizationId").exists().trim(),
|
||||||
|
param("riskId").exists().trim(),
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [ACCEPTED],
|
||||||
|
}),
|
||||||
|
validateRequest,
|
||||||
|
updateRisksStatus
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -46,6 +46,15 @@ export const BadRequestError = (error?: Partial<RequestErrorContext>) => new Req
|
|||||||
stack: error?.stack,
|
stack: error?.stack,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const ResourceNotFound = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||||
|
logLevel: error?.logLevel ?? LogLevel.INFO,
|
||||||
|
statusCode: error?.statusCode ?? 404,
|
||||||
|
type: error?.type ?? "resource_not_found",
|
||||||
|
message: error?.message ?? "The requested resource is not found",
|
||||||
|
context: error?.context,
|
||||||
|
stack: error?.stack,
|
||||||
|
});
|
||||||
|
|
||||||
export const InternalServerError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
export const InternalServerError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||||
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||||
statusCode: error?.statusCode ?? 500,
|
statusCode: error?.statusCode ?? 500,
|
||||||
|
|||||||
@@ -58,6 +58,31 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- infisical-dev
|
- infisical-dev
|
||||||
|
|
||||||
|
git-app:
|
||||||
|
container_name: infisical-dev-git-app
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
- smtp-server
|
||||||
|
- backend
|
||||||
|
- frontend
|
||||||
|
volumes:
|
||||||
|
- ./secret-engine/src:/app/src/ # mounted whole src to avoid missing reload on new files
|
||||||
|
ports:
|
||||||
|
- "3000:3001"
|
||||||
|
build:
|
||||||
|
context: ./secret-engine
|
||||||
|
dockerfile: Dockerfile.dev
|
||||||
|
command: npm run start
|
||||||
|
env_file: ./secret-engine/.env
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=development
|
||||||
|
- MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin
|
||||||
|
networks:
|
||||||
|
- infisical-dev
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
|
||||||
mongo:
|
mongo:
|
||||||
image: mongo
|
image: mongo
|
||||||
container_name: infisical-dev-mongo
|
container_name: infisical-dev-mongo
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ module.exports = {
|
|||||||
root: true,
|
root: true,
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
es2021: true
|
es2021: true,
|
||||||
|
"es6": true
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
"airbnb",
|
"airbnb",
|
||||||
@@ -70,6 +71,7 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
"simple-import-sort/exports": "warn",
|
"simple-import-sort/exports": "warn",
|
||||||
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
"simple-import-sort/imports": [
|
"simple-import-sort/imports": [
|
||||||
"warn",
|
"warn",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -364,6 +364,14 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link href="/secret-scanning" passHref>
|
||||||
|
<a>
|
||||||
|
<MenuItem
|
||||||
|
isSelected={router.asPath === "/secret-scanning"}
|
||||||
|
icon="system-outline-82-extension"
|
||||||
|
>Secret scanning </MenuItem>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
<Link href={`/activity/${currentWorkspace?._id}`} passHref>
|
<Link href={`/activity/${currentWorkspace?._id}`} passHref>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
isSelected={router.asPath === `/activity/${currentWorkspace?._id}`}
|
isSelected={router.asPath === `/activity/${currentWorkspace?._id}`}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will create a new integration session and return it for the given org
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const createNewIntegrationSession = (organizationId: string) =>
|
||||||
|
SecurityClient.fetchCall(`/api/v1/secret-scanning/create-installation-session/organization/${organizationId}`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
}).then(async (res) => {
|
||||||
|
if (res && res.status === 200) {
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
console.log("Failed to create integration session");
|
||||||
|
console.log("response", res)
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createNewIntegrationSession;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will create a new integration session and return it for the given org
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const getInstallationStatus = (organizationId: string) =>
|
||||||
|
SecurityClient.fetchCall(`/api/v1/secret-scanning/installation-status/organization/${organizationId}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
}).then(async (res) => {
|
||||||
|
if (res && res.status === 200) {
|
||||||
|
return (await res.json()).appInstallationComplete;
|
||||||
|
}
|
||||||
|
console.log("Failed to check installation status");
|
||||||
|
console.log("response", res)
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getInstallationStatus;
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
|
|
||||||
|
export type GitRisks = {
|
||||||
|
id: string;
|
||||||
|
description: string;
|
||||||
|
startLine: string;
|
||||||
|
endLine: string;
|
||||||
|
startColumn: string;
|
||||||
|
endColumn: string;
|
||||||
|
match: string;
|
||||||
|
secret: string;
|
||||||
|
file: string;
|
||||||
|
symlinkFile: string;
|
||||||
|
commit: string;
|
||||||
|
entropy: string;
|
||||||
|
author: string;
|
||||||
|
email: string;
|
||||||
|
date: string;
|
||||||
|
message: string;
|
||||||
|
tags: string[];
|
||||||
|
ruleID: string;
|
||||||
|
fingerprint: string;
|
||||||
|
|
||||||
|
isFalsePositive: boolean; // New field for marking risks as false positives
|
||||||
|
isResolved: boolean; // New field for marking risks as resolved
|
||||||
|
riskOwner: string | null; // New field for setting a risk owner (nullable string)
|
||||||
|
installationId: string,
|
||||||
|
repositoryId: string,
|
||||||
|
repositoryLink: string
|
||||||
|
repositoryFullName: string
|
||||||
|
pusher: {
|
||||||
|
name: string,
|
||||||
|
email: string
|
||||||
|
},
|
||||||
|
createdAt: string,
|
||||||
|
organization: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will create a new integration session and return it for the given org
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const getRisksByOrganization = (oranizationId: string): Promise<GitRisks[]> =>
|
||||||
|
SecurityClient.fetchCall(`/api/v1/secret-scanning/organization/${oranizationId}/risks`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
}).then(async (res) => {
|
||||||
|
if (res && res.status === 200) {
|
||||||
|
return (await res.json()).risks;
|
||||||
|
}
|
||||||
|
console.log("Failed to fetch risks");
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getRisksByOrganization;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will create a new integration session and return it for the given org
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const linkGitAppInstallationWithOrganization = (installationId: string, sessionId: string) =>
|
||||||
|
SecurityClient.fetchCall("/api/v1/secret-scanning/link-installation", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
installationId,
|
||||||
|
sessionId
|
||||||
|
})
|
||||||
|
}).then(async (res) => {
|
||||||
|
if (res && res.status === 200) {
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
console.log("Failed to link installation to organization");
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default linkGitAppInstallationWithOrganization;
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
|
|
||||||
|
export enum RiskStatus {
|
||||||
|
RESOLVED_FALSE_POSITIVE = "RESOLVED_FALSE_POSITIVE",
|
||||||
|
RESOLVED_REVOKED = "RESOLVED_REVOKED",
|
||||||
|
RESOLVED_NOT_REVOKED = "RESOLVED_NOT_REVOKED",
|
||||||
|
UNRESOLVED = "UNRESOLVED",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will create a new integration session and return it for the given org
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const updateRiskStatus = (organizationId: string, riskId: string, status: RiskStatus) =>
|
||||||
|
SecurityClient.fetchCall(`/api/v1/secret-scanning/organization/${organizationId}/risks/${riskId}/status`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
status
|
||||||
|
})
|
||||||
|
}).then(async (res) => {
|
||||||
|
if (res && res.status === 200) {
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
console.log("Failed to link installation to organization");
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default updateRiskStatus;
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import { useRouter } from "next/router"
|
||||||
|
|
||||||
|
import createNewIntegrationSession from "../api/secret-scanning/createSecretScanningSession";
|
||||||
|
import getInstallationStatus from "../api/secret-scanning/getInstallationStatus";
|
||||||
|
import getRisksByOrganization, { GitRisks } from "../api/secret-scanning/getRisksByOrganization";
|
||||||
|
import linkGitAppInstallationWithOrganization from "../api/secret-scanning/linkGitAppInstallationWithOrganization";
|
||||||
|
import { RiskStatus } from "../api/secret-scanning/updateRiskStatus";
|
||||||
|
|
||||||
|
export default function SecretScanning() {
|
||||||
|
const router = useRouter()
|
||||||
|
const { state, installationId} = router.query
|
||||||
|
const [integrationEnabled, setIntegrationStatus] = useState(false)
|
||||||
|
const [gitRisks, setGitRisks] = useState<GitRisks[]>([]);
|
||||||
|
const [selectedRiskStatus, setSelectedRiskStatus] = useState("");
|
||||||
|
|
||||||
|
const handleSelectRiskStatusUpdate = (event: any) => {
|
||||||
|
setSelectedRiskStatus(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("selectedRiskStatus===>", selectedRiskStatus)
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
const fetchRisks = async () =>{
|
||||||
|
const risks = await getRisksByOrganization(String(localStorage.getItem("orgData.id")))
|
||||||
|
setGitRisks(risks)
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkInstallation = async () => {
|
||||||
|
if (typeof state === "string" && typeof installationId === "string"){
|
||||||
|
try {
|
||||||
|
await linkGitAppInstallationWithOrganization(installationId as string, state as string)
|
||||||
|
console.log("installation verification complete")
|
||||||
|
}catch (e){
|
||||||
|
console.log("app installation is stale, start new session", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchInstallationStatus = async () => {
|
||||||
|
const status = await getInstallationStatus(String(localStorage.getItem("orgData.id")))
|
||||||
|
setIntegrationStatus(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchInstallationStatus()
|
||||||
|
linkInstallation()
|
||||||
|
fetchRisks()
|
||||||
|
},[state, installationId])
|
||||||
|
|
||||||
|
const generateNewIntegrationSession = async () => {
|
||||||
|
const session = await createNewIntegrationSession(String(localStorage.getItem("orgData.id")))
|
||||||
|
router.push(`https://github.com/apps/infisical-radar/installations/new?state=${session.sessionId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Head>
|
||||||
|
<title>Secret scanning</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
<meta property="og:image" content="/images/message.png" />
|
||||||
|
</Head>
|
||||||
|
{/* <NavHeader pageName={"Secret scanning"} isProjectRelated={false} /> */}
|
||||||
|
|
||||||
|
<div className="text-left">
|
||||||
|
{integrationEnabled ? (
|
||||||
|
<b className="text-green-500">Git app is linked to this organization</b>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||||
|
onClick={generateNewIntegrationSession}
|
||||||
|
>
|
||||||
|
Integrate with GitHub
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="py-3 px-6 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
Date
|
||||||
|
</th>
|
||||||
|
<th className="py-3 px-6 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
Secret Type
|
||||||
|
</th>
|
||||||
|
<th className="py-3 px-6 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
View Risk
|
||||||
|
</th>
|
||||||
|
<th className="py-3 px-6 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
Info
|
||||||
|
</th>
|
||||||
|
<th className="py-3 px-6 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
Status
|
||||||
|
</th>
|
||||||
|
<th className="py-3 px-6 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
Action
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
|
{gitRisks.map((risk) => {
|
||||||
|
return (
|
||||||
|
<tr key={risk.ruleID}>
|
||||||
|
<td className="py-4 px-6 whitespace-nowrap">{risk.createdAt}</td>
|
||||||
|
<td className="py-4 px-6 whitespace-nowrap">{risk.ruleID}</td>
|
||||||
|
<td className="py-4 px-6 whitespace-nowrap">
|
||||||
|
<a
|
||||||
|
href={`https://github.com/${risk.repositoryFullName}/blob/${risk.commit}/${risk.file}#L${risk.startLine}-L${risk.endLine}`}
|
||||||
|
target="_blank"
|
||||||
|
className="text-red-500" rel="noreferrer"
|
||||||
|
>
|
||||||
|
View Exposed Secret
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td className="py-4 px-6 whitespace-nowrap">
|
||||||
|
<div className="font-bold">
|
||||||
|
<a href={`https://github.com/${risk.repositoryFullName}`}>
|
||||||
|
{risk.repositoryFullName}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs">
|
||||||
|
<span>{risk.file}</span><br/>
|
||||||
|
<br/>
|
||||||
|
<span className="font-bold">{risk.author}</span><br/>
|
||||||
|
<span>{risk.email}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="py-4 px-6 whitespace-nowrap">
|
||||||
|
{risk.isResolved ? "Resolved" : "Needs Attention"}
|
||||||
|
</td>
|
||||||
|
<td className="py-4 px-6 whitespace-nowrap">
|
||||||
|
{risk.isResolved ? "Resolved" : "Needs Attention"}
|
||||||
|
</td>
|
||||||
|
<td className="py-4 px-6 whitespace-nowrap">
|
||||||
|
<select
|
||||||
|
value={selectedRiskStatus}
|
||||||
|
onChange={handleSelectRiskStatusUpdate}
|
||||||
|
className="block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
>
|
||||||
|
<option>Unresolved</option>
|
||||||
|
<option value={RiskStatus.RESOLVED_FALSE_POSITIVE}>This is a false positive</option>
|
||||||
|
<option value={RiskStatus.RESOLVED_REVOKED}>I have rotated the secret, resolve risk</option>
|
||||||
|
<option value={RiskStatus.RESOLVED_NOT_REVOKED}>No rotate needed, resolve</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,6 +14,20 @@ server {
|
|||||||
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
|
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /git-app-api {
|
||||||
|
proxy_set_header X-Real-RIP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-NginX-Proxy true;
|
||||||
|
|
||||||
|
proxy_pass http://git-app:3000/;
|
||||||
|
proxy_redirect off;
|
||||||
|
# proxy_redirect http://localhost:8080/ http://frontend.example.com/;
|
||||||
|
|
||||||
|
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
include /etc/nginx/mime.types;
|
include /etc/nginx/mime.types;
|
||||||
|
|
||||||
|
|||||||
Generated
+1939
-1939
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"sourceType": "module",
|
||||||
|
"ecmaVersion": 2020
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"@typescript-eslint"
|
||||||
|
],
|
||||||
|
// "extends": [
|
||||||
|
// "eslint:recommended",
|
||||||
|
// "plugin:@typescript-eslint/recommended"
|
||||||
|
// ]
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
FROM node:18-slim
|
||||||
|
WORKDIR app/
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
RUN npm cache clean --force
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y bash curl && curl -1sLf \
|
||||||
|
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash \
|
||||||
|
&& apt-get update && apt-get install -y infisical=0.8.1
|
||||||
|
|
||||||
|
|
||||||
|
CMD [ "npm", "start" ]
|
||||||
Generated
+2996
-15
File diff suppressed because it is too large
Load Diff
@@ -13,17 +13,24 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "npm run build && probot run ./lib/index.js",
|
"dev": "npm run build && probot run ./lib/index.js",
|
||||||
|
"start": "nodemon -e ts,yml --watch './**/*.ts' --exec npm run dev",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"probot": "^12.2.4"
|
"mongoose": "^7.3.1",
|
||||||
|
"probot": "^12.2.4",
|
||||||
|
"ts-node": "^10.9.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.0.0",
|
"@types/jest": "^29.0.0",
|
||||||
"@types/node": "^18.0.0",
|
"@types/node": "^18.0.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.61.0",
|
||||||
|
"@typescript-eslint/parser": "^5.61.0",
|
||||||
|
"eslint": "^8.44.0",
|
||||||
"jest": "^29.0.0",
|
"jest": "^29.0.0",
|
||||||
"nock": "^13.0.5",
|
"nock": "^13.0.5",
|
||||||
|
"nodemon": "^2.0.22",
|
||||||
"smee-client": "^1.2.2",
|
"smee-client": "^1.2.2",
|
||||||
"ts-jest": "^29.0.0",
|
"ts-jest": "^29.0.0",
|
||||||
"typescript": "^4.1.3"
|
"typescript": "^4.1.3"
|
||||||
|
|||||||
+80
-12
@@ -3,11 +3,9 @@ import { exec } from "child_process";
|
|||||||
import { writeFile, readFile, rm, mkdir, } from "fs";
|
import { writeFile, readFile, rm, mkdir, } from "fs";
|
||||||
import { tmpdir } from "os";
|
import { tmpdir } from "os";
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
|
import mongoose from "mongoose";
|
||||||
// interface CommandResult {
|
import GitRisks from "./models/gitRisks";
|
||||||
// stdout: string;
|
import GitAppOrganizationInstallation from "./models/gitAppOrganizationInstallation";
|
||||||
// stderr: string;
|
|
||||||
// }
|
|
||||||
|
|
||||||
type SecretMatch = {
|
type SecretMatch = {
|
||||||
Description: string;
|
Description: string;
|
||||||
@@ -31,17 +29,41 @@ type SecretMatch = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export = (app: Probot) => {
|
export = (app: Probot) => {
|
||||||
app.on("pull_request", async (context) => {
|
// connect to DB
|
||||||
// create a check
|
initDatabase()
|
||||||
|
|
||||||
|
app.on("installation.created", async (context) => {
|
||||||
|
const { payload } = context;
|
||||||
|
// console.log("payload==>", payload.installation.repository_selection)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on("installation.deleted", async (context) => {
|
||||||
|
const { payload } = context;
|
||||||
|
const { installation, repositories } = payload;
|
||||||
|
if (installation.repository_selection == "all") {
|
||||||
|
await GitRisks.deleteMany({ installationId: installation.id })
|
||||||
|
await GitAppOrganizationInstallation.deleteOne({ installationId: installation.id })
|
||||||
|
} else {
|
||||||
|
for (const repository of repositories) {
|
||||||
|
await GitRisks.deleteMany({ repositoryId: repository.id })
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on("push", async (context) => {
|
app.on("push", async (context) => {
|
||||||
console.log("something was pushed")
|
|
||||||
const { payload } = context;
|
const { payload } = context;
|
||||||
const { commits, repository } = payload;
|
const { commits, repository, installation, } = payload;
|
||||||
const [owner, repo] = repository.full_name.split('/');
|
const [owner, repo] = repository.full_name.split('/');
|
||||||
|
|
||||||
const fingerPrints: any = []
|
const installationLinkToOrgExists = await GitAppOrganizationInstallation.findOne({ installationId: installation.id }).lean()
|
||||||
|
if (!installationLinkToOrgExists) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("installation link does exist!")
|
||||||
|
|
||||||
|
const findingsByFingerprint: { [key: string]: SecretMatch; } = {}
|
||||||
|
|
||||||
for (const commit of commits) {
|
for (const commit of commits) {
|
||||||
for (const filepath of [...commit.added, ...commit.modified]) {
|
for (const filepath of [...commit.added, ...commit.modified]) {
|
||||||
try {
|
try {
|
||||||
@@ -57,7 +79,13 @@ export = (app: Probot) => {
|
|||||||
const findings = await scanContentAndGetFindings(`\n${fileContent}`) // to count lines correctly
|
const findings = await scanContentAndGetFindings(`\n${fileContent}`) // to count lines correctly
|
||||||
|
|
||||||
for (const finding of findings) {
|
for (const finding of findings) {
|
||||||
fingerPrints.push(`${commit.id}:${filepath}:${finding.RuleID}:${finding.StartLine}`)
|
const fingerPrint = `${commit.id}:${filepath}:${finding.RuleID}:${finding.StartLine}`
|
||||||
|
finding.Fingerprint = fingerPrint
|
||||||
|
finding.Commit = commit.id
|
||||||
|
finding.File = filepath
|
||||||
|
finding.Author = commit.author.name
|
||||||
|
finding.Email = commit.author.email
|
||||||
|
findingsByFingerprint[fingerPrint] = finding
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -66,7 +94,19 @@ export = (app: Probot) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("fingerPrints==>", fingerPrints)
|
// change to update
|
||||||
|
for (const key in findingsByFingerprint) {
|
||||||
|
await GitRisks.findOneAndUpdate({ fingerprint: findingsByFingerprint[key].Fingerprint },
|
||||||
|
{
|
||||||
|
...convertKeysToLowercase(findingsByFingerprint[key]),
|
||||||
|
installationId: installation.id,
|
||||||
|
organization: installationLinkToOrgExists.organizationId,
|
||||||
|
repositoryFullName: repository.full_name,
|
||||||
|
repositoryId: repository.id
|
||||||
|
}, {
|
||||||
|
upsert: true
|
||||||
|
})
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -149,3 +189,31 @@ function deleteTempFolder(folderPath: string): Promise<void> {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const initDatabase = async () => {
|
||||||
|
try {
|
||||||
|
await mongoose.connect(process.env.MONGO_URL);
|
||||||
|
// allow empty strings to pass the required validator
|
||||||
|
mongoose.Schema.Types.String.checkRequired(v => typeof v === "string");
|
||||||
|
|
||||||
|
console.log("Database connection established");
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`Unable to establish Database connection due to the error.\n${err}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mongoose.connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertKeysToLowercase<T>(obj: T): T {
|
||||||
|
const convertedObj = {} as T;
|
||||||
|
|
||||||
|
for (const key in obj) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||||
|
const lowercaseKey = key.charAt(0).toLowerCase() + key.slice(1);
|
||||||
|
convertedObj[lowercaseKey] = obj[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertedObj;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Schema, model } from "mongoose";
|
||||||
|
|
||||||
|
type Installation = {
|
||||||
|
installationId: string
|
||||||
|
organizationId: string
|
||||||
|
user: Schema.Types.ObjectId
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const gitAppOrganizationInstallation = new Schema<Installation>({
|
||||||
|
installationId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
organizationId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "User",
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const GitAppOrganizationInstallation = model<Installation>("git_app_organization_installation", gitAppOrganizationInstallation);
|
||||||
|
|
||||||
|
export default GitAppOrganizationInstallation;
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import { Schema, model } from "mongoose";
|
||||||
|
export const STATUS_RESOLVED_FALSE_POSITIVE = 'RESOLVED_FALSE_POSITIVE';
|
||||||
|
export const STATUS_RESOLVED_REVOKED = 'RESOLVED_REVOKED';
|
||||||
|
export const STATUS_RESOLVED_NOT_REVOKED = 'RESOLVED_NOT_REVOKED';
|
||||||
|
export const STATUS_UNRESOLVED = 'UNRESOLVED';
|
||||||
|
|
||||||
|
export type GitRisks = {
|
||||||
|
id: string;
|
||||||
|
description: string;
|
||||||
|
startLine: string;
|
||||||
|
endLine: string;
|
||||||
|
startColumn: string;
|
||||||
|
endColumn: string;
|
||||||
|
match: string;
|
||||||
|
secret: string;
|
||||||
|
file: string;
|
||||||
|
symlinkFile: string;
|
||||||
|
commit: string;
|
||||||
|
entropy: string;
|
||||||
|
author: string;
|
||||||
|
email: string;
|
||||||
|
date: string;
|
||||||
|
message: string;
|
||||||
|
tags: string[];
|
||||||
|
ruleID: string;
|
||||||
|
fingerprint: string;
|
||||||
|
|
||||||
|
isFalsePositive: boolean; // New field for marking risks as false positives
|
||||||
|
isResolved: boolean; // New field for marking risks as resolved
|
||||||
|
riskOwner: string | null; // New field for setting a risk owner (nullable string)
|
||||||
|
installationId: string,
|
||||||
|
repositoryId: string,
|
||||||
|
repositoryLink: string
|
||||||
|
repositoryFullName: string
|
||||||
|
status: string
|
||||||
|
pusher: {
|
||||||
|
name: string,
|
||||||
|
email: string
|
||||||
|
},
|
||||||
|
organization: Schema.Types.ObjectId,
|
||||||
|
}
|
||||||
|
|
||||||
|
const gitRisks = new Schema<GitRisks>({
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
startLine: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
endLine: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
startColumn: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
endColumn: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
file: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
symlinkFile: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
commit: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
entropy: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
author: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
type: [String],
|
||||||
|
},
|
||||||
|
ruleID: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
fingerprint: {
|
||||||
|
type: String,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
isFalsePositive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isResolved: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
riskOwner: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
installationId: {
|
||||||
|
type: String,
|
||||||
|
require: true
|
||||||
|
},
|
||||||
|
repositoryId: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
repositoryLink: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
repositoryFullName: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
pusher: {
|
||||||
|
name: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
},
|
||||||
|
organization: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Organization",
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
STATUS_RESOLVED_FALSE_POSITIVE,
|
||||||
|
STATUS_RESOLVED_REVOKED,
|
||||||
|
STATUS_RESOLVED_NOT_REVOKED,
|
||||||
|
STATUS_UNRESOLVED
|
||||||
|
],
|
||||||
|
default: STATUS_UNRESOLVED
|
||||||
|
}
|
||||||
|
}, { timestamps: true });
|
||||||
|
|
||||||
|
const GitRisks = model<GitRisks>("GitRisks", gitRisks);
|
||||||
|
|
||||||
|
export default GitRisks;
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
/* Strict Type-Checking Options */
|
/* Strict Type-Checking Options */
|
||||||
"strict": true /* Enable all strict type-checking options. */,
|
"strict": false /* Enable all strict type-checking options. */,
|
||||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
|||||||
Reference in New Issue
Block a user