@catladder/cli 1.135.0 → 1.136.0

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.
Files changed (41) hide show
  1. package/bin/catci +3 -0
  2. package/bin/catci-dev +3 -0
  3. package/dist/apps/catci/catci.d.ts +1 -0
  4. package/dist/apps/catci/catci.js +71 -0
  5. package/dist/apps/catci/catci.js.map +1 -0
  6. package/dist/apps/catci/commands/security/auditDocument.d.ts +19 -0
  7. package/dist/apps/catci/commands/security/auditDocument.js +90 -0
  8. package/dist/apps/catci/commands/security/auditDocument.js.map +1 -0
  9. package/dist/apps/catci/commands/security/commands.d.ts +2 -0
  10. package/dist/apps/catci/commands/security/commands.js +175 -0
  11. package/dist/apps/catci/commands/security/commands.js.map +1 -0
  12. package/dist/apps/catci/commands/security/createSecurityAuditMergeRequest.d.ts +9 -0
  13. package/dist/apps/catci/commands/security/createSecurityAuditMergeRequest.js +112 -0
  14. package/dist/apps/catci/commands/security/createSecurityAuditMergeRequest.js.map +1 -0
  15. package/dist/apps/catci/commands/security/evaluateSecurityAudit.d.ts +5 -0
  16. package/dist/apps/catci/commands/security/evaluateSecurityAudit.js +76 -0
  17. package/dist/apps/catci/commands/security/evaluateSecurityAudit.js.map +1 -0
  18. package/dist/apps/catci/commands/security/topics.json +112 -0
  19. package/dist/apps/cli/commands/project/commandSecurityEvaluate.d.ts +3 -0
  20. package/dist/apps/cli/commands/project/commandSecurityEvaluate.js +70 -0
  21. package/dist/apps/cli/commands/project/commandSecurityEvaluate.js.map +1 -0
  22. package/dist/apps/cli/commands/project/index.js +2 -0
  23. package/dist/apps/cli/commands/project/index.js.map +1 -1
  24. package/dist/bundles/catci/index.js +41 -0
  25. package/dist/bundles/catenv/index.js +1 -1
  26. package/dist/bundles/cli/index.js +3 -3
  27. package/dist/catci.d.ts +1 -0
  28. package/dist/catci.js +5 -0
  29. package/dist/catci.js.map +1 -0
  30. package/dist/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +6 -5
  32. package/scripts/bundle +2 -0
  33. package/src/apps/catci/catci.ts +20 -0
  34. package/src/apps/catci/commands/security/auditDocument.ts +150 -0
  35. package/src/apps/catci/commands/security/commands.ts +146 -0
  36. package/src/apps/catci/commands/security/createSecurityAuditMergeRequest.ts +98 -0
  37. package/src/apps/catci/commands/security/evaluateSecurityAudit.ts +30 -0
  38. package/src/apps/catci/commands/security/topics.json +120 -0
  39. package/src/apps/cli/commands/project/commandSecurityEvaluate.ts +26 -0
  40. package/src/apps/cli/commands/project/index.ts +2 -0
  41. package/src/catci.ts +3 -0
package/package.json CHANGED
@@ -52,7 +52,7 @@
52
52
  }
53
53
  ],
54
54
  "license": "MIT",
55
- "version": "1.135.0",
55
+ "version": "1.136.0",
56
56
  "scripts": {
57
57
  "lint": "eslint \"src/**/*.ts\"",
58
58
  "lint:fix": "eslint \"src/**/*.ts\" --fix",
@@ -63,17 +63,16 @@
63
63
  "build:watch": "yarn tsc -w"
64
64
  },
65
65
  "bin": {
66
+ "catci": "./bin/catci",
66
67
  "catenv": "./bin/catenv",
67
68
  "catladder": "./bin/catladder"
68
69
  },
69
- "dependencies": {
70
- "ts-node": "^10.9.1"
71
- },
72
70
  "engines": {
73
71
  "node": ">=12.0.0"
74
72
  },
75
73
  "devDependencies": {
76
- "@catladder/pipeline": "1.135.0",
74
+ "@catladder/pipeline": "1.136.0",
75
+ "@gitbeaker/rest": "^39.28.0",
77
76
  "@kubernetes/client-node": "^0.16.2",
78
77
  "@tsconfig/node14": "^1.0.1",
79
78
  "@types/common-tags": "^1.8.0",
@@ -104,6 +103,8 @@
104
103
  "open": "^8.4.0",
105
104
  "prettier": "^2.5.1",
106
105
  "tmp-promise": "^2.0.2",
106
+ "ts-node": "^10.9.1",
107
+ "ts-results-es": "^4.1.0-alpha.1",
107
108
  "typescript": "^4.5.4",
108
109
  "vorpal": "^1.12.0"
109
110
  }
package/scripts/bundle CHANGED
@@ -2,3 +2,5 @@
2
2
  ncc build dist/catenv.js -o dist/bundles/catenv -e 'ts-node' $( ((SHOULD_MINIFY == 1)) && printf %s '-m')
3
3
 
4
4
  ncc build dist/cli.js -o dist/bundles/cli -e 'ts-node' $( ((SHOULD_MINIFY == 1)) && printf %s '-m')
5
+
6
+ ncc build dist/catci.js -o dist/bundles/catci -e 'ts-node' $( ((SHOULD_MINIFY == 1)) && printf %s '-m')
@@ -0,0 +1,20 @@
1
+ import Vorpal from "vorpal";
2
+ import packageInfo from "../../packageInfos";
3
+ import securityCommands from "./commands/security/commands";
4
+
5
+ export async function runCatCi() {
6
+ const vorpal = new Vorpal();
7
+
8
+ process.exitCode = 0;
9
+ vorpal.delimiter("catci $").history("catci").version(packageInfo.version);
10
+
11
+ securityCommands(vorpal);
12
+
13
+ const isInteractive = process.argv.length <= 2;
14
+ if (isInteractive) {
15
+ vorpal.log(`Catladder CI Tools 😻🔨 version ${packageInfo.version}`).show();
16
+ } else {
17
+ await vorpal.exec(process.argv.slice(2).join(" "));
18
+ process.exit();
19
+ }
20
+ }
@@ -0,0 +1,150 @@
1
+ import topicsJson from "./topics.json";
2
+
3
+ type Topic = {
4
+ description: string;
5
+ responsibles: number;
6
+ more: string;
7
+ };
8
+
9
+ const allTopics: Topic[] = topicsJson;
10
+
11
+ const checkYes = "✅";
12
+ const checkNo = "❌";
13
+
14
+ const checkPlaceholder = `${checkYes}/${checkNo}`;
15
+ const responsiblePlaceholder = "@...";
16
+ const rows = [
17
+ ["Responsible", checkPlaceholder, "Description", "Note", "More Information"],
18
+ ].concat(
19
+ allTopics.map((t) => [
20
+ Array(t.responsibles).fill(responsiblePlaceholder).join(", "),
21
+ checkPlaceholder,
22
+ t.description,
23
+ "",
24
+ t.more,
25
+ ])
26
+ );
27
+
28
+ function makeTable(rows: string[][]) {
29
+ const colWidths = calculateColumnWidths(rows);
30
+
31
+ return `
32
+ ${makeRow(rows[0], colWidths, " ")}
33
+ ${makeRow(
34
+ rows[0].map(() => ""),
35
+ colWidths,
36
+ "-"
37
+ )}
38
+ ${rows
39
+ .slice(1)
40
+ .map((row) => makeRow(row, colWidths, " "))
41
+ .join("\n")}
42
+ `;
43
+ }
44
+
45
+ function calculateColumnWidths(rows: string[][]) {
46
+ const columnCount = rows[0].length;
47
+ return Array.from({ length: columnCount }, (_, i) => i).map((columnIndex) =>
48
+ Math.max(...rows.map((row) => row[columnIndex].length))
49
+ );
50
+ }
51
+
52
+ function makeRow(row: string[], colWidths: number[], fillString: string) {
53
+ return `| ${row
54
+ .map((cell, i) => cell.padEnd(colWidths[i], fillString))
55
+ .join(" | ")} |`;
56
+ }
57
+
58
+ export function makeTemplate() {
59
+ return `
60
+ # Security Audit Report
61
+
62
+ A security audit report document is a comprehensive assessment of an application's security posture, containing security topics that auditors can mark to indicate the state of various security aspects.
63
+
64
+ It serves as a structured guide for security team to evaluate different security factors such as authentication, authorization, data encryption, input validation, and more.
65
+
66
+ ## General Information
67
+
68
+ - Project Owner is @...
69
+ - Dev team:
70
+ - @...
71
+ - @...
72
+ - @...
73
+
74
+ ## Project Security
75
+
76
+ ${makeTable(rows)}
77
+
78
+ `;
79
+ }
80
+
81
+ export type SecurityEvaluation = {
82
+ topics: {
83
+ description: string;
84
+ responsibles: string[];
85
+ note: string;
86
+ isUnknown: boolean;
87
+ isAnswered: boolean;
88
+ isSecured: boolean;
89
+ }[];
90
+ score: {
91
+ rating: number;
92
+ totalTopics: number;
93
+ answeredTopics: number;
94
+ securedTopics: number;
95
+ unknownTopics: number;
96
+ };
97
+ };
98
+
99
+ export function evaluateDocument(document: string): SecurityEvaluation {
100
+ const rawRows =
101
+ document.match(/^\s*\|.*?\|\s*$/gm)?.map((row) => row.trim()) ?? [];
102
+ const matchedRows = rawRows
103
+ .map((row) => row.split("|").map((col) => col.trim()))
104
+ .slice(2);
105
+ const knownTopics = new Set(allTopics.map((t) => t.description));
106
+
107
+ const topics = matchedRows.map((col) => {
108
+ const responsibles = col[1].split(", ");
109
+ const answer = col[2];
110
+ const description = col[3];
111
+ const note = col[4];
112
+
113
+ const isUnknown = !knownTopics.has(description);
114
+ const isAnswered =
115
+ !isUnknown &&
116
+ !answer.includes(checkPlaceholder) &&
117
+ !responsibles.some((responsible) =>
118
+ responsible.includes(responsiblePlaceholder)
119
+ );
120
+ const isSecured = !isUnknown && isAnswered && answer.includes(checkYes);
121
+
122
+ return {
123
+ responsibles,
124
+ answer,
125
+ description,
126
+ note,
127
+ isUnknown,
128
+ isAnswered,
129
+ isSecured,
130
+ };
131
+ });
132
+
133
+ const totalTopics = allTopics.length;
134
+ const answeredTopics = topics.filter((t) => t.isAnswered).length;
135
+ const securedTopics = topics.filter((t) => t.isSecured).length;
136
+ const unknownTopics = topics.filter((t) => t.isUnknown).length;
137
+
138
+ const rating = Math.round((securedTopics / totalTopics) * 100);
139
+
140
+ return {
141
+ topics,
142
+ score: {
143
+ rating,
144
+ totalTopics,
145
+ answeredTopics,
146
+ securedTopics,
147
+ unknownTopics,
148
+ },
149
+ };
150
+ }
@@ -0,0 +1,146 @@
1
+ import type Vorpal from "vorpal";
2
+ import {
3
+ evaluateSecurityAudit,
4
+ makeSecurityAuditOverview,
5
+ } from "./evaluateSecurityAudit";
6
+ import { Gitlab } from "@gitbeaker/rest";
7
+ import {
8
+ SECURITY_AUDIT_FILE_NAME,
9
+ createSecurityAuditMergeRequest,
10
+ } from "./createSecurityAuditMergeRequest";
11
+
12
+ const GITLAB_HOST = "https://git.panter.ch";
13
+
14
+ export default function (vorpal: Vorpal) {
15
+ commandCiJob(vorpal);
16
+ commandEvaluate(vorpal);
17
+ commandCreate(vorpal);
18
+ }
19
+
20
+ async function commandCiJob(vorpal: Vorpal) {
21
+ vorpal
22
+ .command(
23
+ "security-audit-ci-job <path> <token> <mainBranch> <projectId> <userId>",
24
+ `Evaluates security audit document. If the document can't be evaluated or does not exist, creates a new MR with security audit document template.
25
+
26
+ <path> root path of a project with security audit document (${SECURITY_AUDIT_FILE_NAME})
27
+ <token> gitlab token with 'api' scopes and permissions to create a new branch
28
+ <main-branch> main branch name
29
+ <project-id> project id to create security audit for
30
+ <user-id> gitlab user id that will be assignee of the audit
31
+ `
32
+ )
33
+ .action(async (args) => {
34
+ const evaluation = await evaluateSecurityAudit({ path: args.path });
35
+
36
+ if (evaluation.isErr()) {
37
+ console.log("could not evaluate security audit document");
38
+ console.log(
39
+ "creating new merge request with security audit template..."
40
+ );
41
+
42
+ const { token, mainBranch, projectId, userId } = args;
43
+ const api = new Gitlab({
44
+ host: GITLAB_HOST,
45
+ token,
46
+ });
47
+
48
+ const mr = await createSecurityAuditMergeRequest({
49
+ api,
50
+ mainBranch,
51
+ projectId,
52
+ userId: parseInt(userId),
53
+ });
54
+
55
+ if (mr.isErr()) {
56
+ console.error(
57
+ `could not create merge request with security audit template: ${mr.error}`
58
+ );
59
+ process.exitCode = 1;
60
+ return;
61
+ }
62
+
63
+ console.log("security audit merge request created successfully");
64
+ console.log(
65
+ `please finish the MR by updating SECURITY.md document: ${mr.value.web_url}`
66
+ );
67
+ process.exitCode = 1;
68
+ return;
69
+ }
70
+
71
+ if (evaluation.value.score.answeredTopics === 0) {
72
+ console.error("audit document has no answered topics");
73
+ console.error(
74
+ `please answer security topics in ${SECURITY_AUDIT_FILE_NAME} by adding responsible people and check/cross in the table`
75
+ );
76
+ process.exitCode = 1;
77
+ return;
78
+ }
79
+
80
+ process.exitCode = 0;
81
+ console.log(makeSecurityAuditOverview(evaluation.value));
82
+ });
83
+ }
84
+
85
+ async function commandEvaluate(vorpal: Vorpal) {
86
+ vorpal
87
+ .command(
88
+ "security-audit-evaluate <path>",
89
+ "Evaluates security audit document in given <path>"
90
+ )
91
+ .action(async (args) => {
92
+ console.log("evaluating security audit document...");
93
+
94
+ const result = await evaluateSecurityAudit({ path: args.path });
95
+ if (result.isErr()) {
96
+ console.error(result.error);
97
+ console.error(
98
+ `please make sure the security audit document ${SECURITY_AUDIT_FILE_NAME} is in the repository`
99
+ );
100
+ process.exitCode = 1;
101
+ } else {
102
+ console.log(makeSecurityAuditOverview(result.value));
103
+ }
104
+ });
105
+ }
106
+
107
+ async function commandCreate(vorpal: Vorpal) {
108
+ vorpal
109
+ .command(
110
+ "security-audit-create <token> <mainBranch> <projectId> <userId>",
111
+ `Creates a MR in given project with the latest security audit template document
112
+
113
+ <token> gitlab token with 'api' scopes and permissions to create a new branch
114
+ <main-branch> main branch name
115
+ <project-id> project id to create security audit for
116
+ <user-id> gitlab user id that will be assignee of the audit
117
+ `
118
+ )
119
+ .action(async (args) => {
120
+ const { token, mainBranch, projectId, userId } = args;
121
+
122
+ const api = new Gitlab({
123
+ host: GITLAB_HOST,
124
+ token,
125
+ });
126
+
127
+ const result = await createSecurityAuditMergeRequest({
128
+ api,
129
+ mainBranch,
130
+ projectId,
131
+ userId: parseInt(userId),
132
+ });
133
+
134
+ if (result.isErr()) {
135
+ console.error(
136
+ `could not create security audit merge request: ${result.error}`
137
+ );
138
+ process.exitCode = 1;
139
+ } else {
140
+ console.log("security audit merge request created successfully");
141
+ console.log(
142
+ `please finish the MR by updating SECURITY.md document: ${result.value.web_url}`
143
+ );
144
+ }
145
+ });
146
+ }
@@ -0,0 +1,98 @@
1
+ import type { Gitlab } from "@gitbeaker/core";
2
+ import { Err, Result } from "ts-results-es";
3
+ import { makeTemplate } from "./auditDocument";
4
+
5
+ function makeDatedBranchName(branchName: string) {
6
+ const date = new Date().toISOString().slice(0, -5).replaceAll(/[:.T]/g, "-");
7
+ return `${branchName}-${date}`;
8
+ }
9
+
10
+ const MR_TITLE = "Draft: chore(security): add security audit document";
11
+ export const SECURITY_AUDIT_FILE_NAME = "SECURITY.md" as const;
12
+
13
+ export async function createSecurityAuditMergeRequest({
14
+ projectId,
15
+ mainBranch,
16
+ userId,
17
+ api,
18
+ }: {
19
+ projectId: string;
20
+ mainBranch: string;
21
+ userId: number;
22
+ api: Gitlab;
23
+ }) {
24
+ const mrs = (
25
+ await Result.wrapAsync(() =>
26
+ api.MergeRequests.all({
27
+ state: "opened",
28
+ wip: "yes",
29
+ labels: "security-audit",
30
+ })
31
+ )
32
+ ).mapErr(() => `could not search for existing merge requests` as const);
33
+ if (mrs.isErr()) return mrs;
34
+
35
+ const existingMr = mrs.value[0];
36
+ if (existingMr)
37
+ return Err(
38
+ `open merge request with security audit already exists: ${existingMr.web_url}`
39
+ );
40
+
41
+ const auditTemplate = Result.wrap(() => makeTemplate()).mapErr(
42
+ () => "could not make security audit template document" as const
43
+ );
44
+ if (auditTemplate.isErr()) return auditTemplate;
45
+
46
+ const branch = (
47
+ await Result.wrapAsync(() =>
48
+ api.Branches.create(
49
+ projectId,
50
+ makeDatedBranchName("chore/security-audit"),
51
+ mainBranch
52
+ )
53
+ )
54
+ ).mapErr((e) => {
55
+ console.log(e);
56
+ return "could not create branch" as const;
57
+ });
58
+ if (branch.isErr()) return branch;
59
+
60
+ const commit = (
61
+ await Result.wrapAsync(() =>
62
+ api.Commits.create(
63
+ projectId,
64
+ branch.value.name,
65
+ "chore(security): add empty security audit document template",
66
+ [
67
+ {
68
+ action: "create",
69
+ filePath: SECURITY_AUDIT_FILE_NAME,
70
+ content: auditTemplate.value,
71
+ encoding: "text",
72
+ },
73
+ ]
74
+ )
75
+ )
76
+ ).mapErr(() => "could not create commit" as const);
77
+ if (commit.isErr()) return commit;
78
+
79
+ const mr = (
80
+ await Result.wrapAsync(() =>
81
+ api.MergeRequests.create(
82
+ projectId,
83
+ branch.value.name,
84
+ mainBranch,
85
+ MR_TITLE,
86
+ {
87
+ description: `Please follow and update security audit document in \`${SECURITY_AUDIT_FILE_NAME}\`.`,
88
+ assigneeId: userId,
89
+ squash: true,
90
+ labels: "security-audit",
91
+ removeSourceBranch: true,
92
+ }
93
+ )
94
+ )
95
+ ).mapErr(() => "could not create merge request" as const);
96
+
97
+ return mr;
98
+ }
@@ -0,0 +1,30 @@
1
+ import { Result } from "ts-results-es";
2
+ import { join } from "path";
3
+ import { readFile } from "fs/promises";
4
+ import { SECURITY_AUDIT_FILE_NAME } from "./createSecurityAuditMergeRequest";
5
+ import type { SecurityEvaluation } from "./auditDocument";
6
+ import { evaluateDocument } from "./auditDocument";
7
+
8
+ export async function evaluateSecurityAudit({ path }: { path: string }) {
9
+ return (
10
+ await Result.wrapAsync(async () => {
11
+ const filePath = join(path, SECURITY_AUDIT_FILE_NAME);
12
+ const docData = await readFile(filePath);
13
+ const doc = docData.toString("utf-8");
14
+ return evaluateDocument(doc);
15
+ })
16
+ ).mapErr((e) => `could not evaluate ${SECURITY_AUDIT_FILE_NAME}: ${e}`);
17
+ }
18
+
19
+ export function makeSecurityAuditOverview(evaluation: SecurityEvaluation) {
20
+ const ratingToEmo = (r: number) => (r < 33 ? "🟥" : r < 66 ? "🟨" : "🟩");
21
+
22
+ return `Project security posture overview:
23
+ 🧐 Total topics: ${evaluation.score.totalTopics}
24
+ 🔒 Secured topics: ${evaluation.score.securedTopics}
25
+ 📢 Answered topics: ${evaluation.score.answeredTopics}
26
+ ❔ Unknown topics: ${evaluation.score.unknownTopics}
27
+ 📊 Rating: ${ratingToEmo(evaluation.score.rating)} ${
28
+ evaluation.score.rating
29
+ }/100`;
30
+ }
@@ -0,0 +1,120 @@
1
+ [
2
+ {
3
+ "description": "No API keys or secrets are stored in repository",
4
+ "responsibles": 1,
5
+ "more": ""
6
+ },
7
+ {
8
+ "description": "The app does not provide password login",
9
+ "responsibles": 1,
10
+ "more": ""
11
+ },
12
+ {
13
+ "description": "Passwords are not stored",
14
+ "responsibles": 1,
15
+ "more": ""
16
+ },
17
+ {
18
+ "description":
19
+ "Passwords are stored hashed with salt and salt is not stored in the repository",
20
+ "responsibles": 1,
21
+ "more": "[guide](https://git.panter.ch/panter/security-guide/-/blob/main/docs/audit/hash.md)"
22
+ },
23
+ {
24
+ "description": "Input that ends up in DOM is properly sanitized",
25
+ "responsibles": 1,
26
+ "more": "[guide](https://git.panter.ch/panter/security-guide/-/blob/main/docs/audit/xss.md)"
27
+ },
28
+ {
29
+ "description": "All user inputs have reasonable validations",
30
+ "responsibles": 1,
31
+ "more": "[guide](https://git.panter.ch/panter/security-guide/-/blob/main/docs/audit/validation.md)"
32
+ },
33
+ {
34
+ "description": "The app is not using cookies",
35
+ "responsibles": 1,
36
+ "more": "[guide](https://git.panter.ch/panter/security-guide/-/blob/main/docs/audit/cookies.md)"
37
+ },
38
+ {
39
+ "description": "The app is using cookies and cookies are properly configured",
40
+ "responsibles": 1,
41
+ "more": "[guide](https://git.panter.ch/panter/security-guide/-/blob/main/docs/audit/cookies.md)"
42
+ },
43
+ {
44
+ "description":
45
+ "The app uses JWT with a secret and the secret is not stored in the repository",
46
+ "responsibles": 1,
47
+ "more": "[guide](https://git.panter.ch/panter/security-guide/-/blob/main/docs/audit/cookies.md)"
48
+ },
49
+ {
50
+ "description": "Authorization and user roles (RBAC) were reviewed thoroughly",
51
+ "responsibles": 2,
52
+ "more": "[guide](https://git.panter.ch/panter/security-guide/-/blob/main/docs/audit/authorization.md)"
53
+ },
54
+ {
55
+ "description": "CORS headers do not use `*`",
56
+ "responsibles": 1,
57
+ "more": "[guide](https://git.panter.ch/panter/security-guide/-/blob/main/docs/audit/cors.md)"
58
+ },
59
+ {
60
+ "description":
61
+ "CSP headers are properly configured (no `unsafe-inline` or `unsafe-eval`)",
62
+ "responsibles": 1,
63
+ "more": "[guide](https://git.panter.ch/panter/security-guide/-/blob/main/docs/audit/csp.md)"
64
+ },
65
+ {
66
+ "description": "DoS defense mechanism is implemented",
67
+ "responsibles": 1,
68
+ "more": "[guide](https://git.panter.ch/panter/security-guide/-/blob/main/docs/audit/dos.md)"
69
+ },
70
+ {
71
+ "description":
72
+ "YAML/XML parsing is not used or used YAML/XML parsers have disabled DTD",
73
+ "responsibles": 1,
74
+ "more": "[guide](https://git.panter.ch/panter/security-guide/-/blob/main/docs/audit/dos.md)"
75
+ },
76
+ {
77
+ "description": "The app implements CSRF prevention",
78
+ "responsibles": 1,
79
+ "more": "[guide](https://git.panter.ch/panter/security-guide/-/blob/main/docs/audit/csrf.md)"
80
+ },
81
+ {
82
+ "description": "The app has a rate limitter",
83
+ "responsibles": 1,
84
+ "more": ""
85
+ },
86
+ {
87
+ "description":
88
+ "The app has disabled GraphQL introspection and schema registry",
89
+ "responsibles": 1,
90
+ "more": "[guide](https://git.panter.ch/panter/security-guide/-/blob/main/docs/audit/graphql.md)"
91
+ },
92
+ {
93
+ "description": "The app has set GraphQL complexity query limits",
94
+ "responsibles": 1,
95
+ "more": "[guide](https://git.panter.ch/panter/security-guide/-/blob/main/docs/audit/graphql.md)"
96
+ },
97
+ {
98
+ "description": "`sitemap.xml` does not leak any routes with sensitive data",
99
+ "responsibles": 1,
100
+ "more": ""
101
+ },
102
+ {
103
+ "description":
104
+ "Cloud storage is (private) configured to not leak any sensitive data publicly",
105
+ "responsibles": 1,
106
+ "more": ""
107
+ },
108
+ {
109
+ "description":
110
+ "Security Dashboard checks weekly vulnerable dependencies https://dep.panter.swiss/",
111
+ "responsibles": 1,
112
+ "more": ""
113
+ },
114
+ {
115
+ "description":
116
+ "The app has `.well-known/security.txt` https://securitytxt.org/",
117
+ "responsibles": 1,
118
+ "more": ""
119
+ }
120
+ ]
@@ -0,0 +1,26 @@
1
+ import type Vorpal from "vorpal";
2
+ import {
3
+ evaluateSecurityAudit,
4
+ makeSecurityAuditOverview,
5
+ } from "../../../catci/commands/security/evaluateSecurityAudit";
6
+ import { getGitRoot } from "../../../../utils/projects";
7
+
8
+ export default async (vorpal: Vorpal) => {
9
+ vorpal
10
+ .command(
11
+ "project-security-evaluate",
12
+ "evaluate project's security audit document"
13
+ )
14
+ .action(async function () {
15
+ const gitRoot = await getGitRoot();
16
+ const result = await evaluateSecurityAudit({ path: gitRoot });
17
+ if (result.isErr()) {
18
+ console.error(
19
+ "Could not evaluate security audit document:",
20
+ result.error
21
+ );
22
+ } else {
23
+ console.log(makeSecurityAuditOverview(result.value));
24
+ }
25
+ });
26
+ };
@@ -26,6 +26,7 @@ import commandTriggerCronjob from "./commandTriggerCronjob";
26
26
  import commandOpenGrafanaPod from "./commandOpenGrafanaPod";
27
27
  import commandSecretsClearBackups from "./commandSecretsClearBackups";
28
28
  import commandProjectRestoreDb from "./cloudSql/commandProjectRestoreDb";
29
+ import commandSecurityEvaluate from "./commandSecurityEvaluate";
29
30
 
30
31
  export default async (vorpal: Vorpal) => {
31
32
  commandSetup(vorpal);
@@ -61,4 +62,5 @@ export default async (vorpal: Vorpal) => {
61
62
 
62
63
  commandGetMyTotalWorktime(vorpal);
63
64
  commandMigrateHelm3(vorpal);
65
+ commandSecurityEvaluate(vorpal);
64
66
  };
package/src/catci.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { runCatCi } from "./apps/catci/catci";
2
+
3
+ runCatCi();