Skip to content

Commit

Permalink
feat(cli): Secret scan (#438)
Browse files Browse the repository at this point in the history
Co-authored-by: kriptonian1 <shymal.sawan@gmail.com>
  • Loading branch information
Sambit003 and kriptonian1 authored Sep 17, 2024
1 parent af06071 commit 85cb8ab
Show file tree
Hide file tree
Showing 88 changed files with 6,627 additions and 1,801 deletions.
8 changes: 6 additions & 2 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@
"eccrypto": "^1.1.6",
"figlet": "^1.7.0",
"fs": "0.0.1-security",
"glob": "^11.0.0",
"nodemon": "^3.1.4",
"socket.io-client": "^4.7.5"
"secret-scan": "workspace:*",
"socket.io-client": "^4.7.5",
"typescript": "^5.5.2"
},
"devDependencies": {
"@swc/cli": "^0.4.0",
Expand All @@ -32,6 +35,7 @@
"@types/figlet": "^1.5.8",
"@types/eccrypto": "^1.1.6",
"@types/node": "^20.14.10",
"eslint-config-standard-with-typescript": "^43.0.1"
"eslint-config-standard-with-typescript": "^43.0.1",
"tsup": "^8.1.2"
}
}
179 changes: 179 additions & 0 deletions apps/cli/src/commands/scan.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import BaseCommand from '@/commands/base.command'
import type {
CommandActionData,
CommandOption
} from '@/types/command/command.types'
import { execSync } from 'child_process'
import { readFileSync, statSync } from 'fs'
import { globSync } from 'glob'
import path from 'path'
import secretDetector from 'secret-scan'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const colors = require('colors/safe')

export default class ScanCommand extends BaseCommand {
getOptions(): CommandOption[] {
return [
{
short: '-f',
long: '--file <string>',
description: 'Scan a specific file'
},
{
short: '-c',
long: '--current-changes',
description:
'Scan only the current changed files that are not committed'
}
]
}

getName(): string {
return 'scan'
}

getDescription(): string {
return 'Scan the project to detect any hardcoded secrets'
}

action({ options }: CommandActionData): Promise<void> | void {
const { currentChanges, file } = options

console.log('\n=============================')
console.log(colors.cyan.bold('🔍 Secret Scan Started'))
console.log('=============================')

if (file) {
console.log(`Scanning file: ${file}`)
this.scanFiles([file as string])
return
}
if (currentChanges) {
console.log('Scanning only the current changes')
const files = this.getChangedFiles()
this.scanFiles(files)
} else {
console.log('\n\n📂 Scanning all files...\n')
const files = this.getAllFiles()
this.scanFiles(files)
}
}

private scanFiles(allfiles: string[]) {
const foundSecrets = []
let skipNextLine = false
for (const file of allfiles) {
const stats = statSync(file)
if (stats.isFile()) {
const content = readFileSync(file, 'utf8').split(/\r?\n/)

// Skip the file if ignore comment is found in the first line
if (content[0].includes('keyshade-ignore-all')) {
continue
}

content.forEach((line, index) => {
// Skip the next line if ignore comment is found in the previous line
if (skipNextLine) {
skipNextLine = false
return
}

if (line.includes('keyshade-ignore')) {
skipNextLine = true
return
}
const { found, regex } = secretDetector.detect(line)
if (found) {
const matched = line.match(regex)
const highlightedLine = line
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
.replace(regex, colors.red.underline(matched[0]) as string)
.trim()
foundSecrets.push({
file,
line: index + 1,
content: highlightedLine
})
}
})
}
}
if (foundSecrets.length > 0) {
console.log(
colors.red(`\n 🚨 Found ${foundSecrets.length} hardcoded secrets:\n`)
)
foundSecrets.forEach((secret) => {
console.log(
`${colors.underline(`${secret.file}:${secret.line}`)}: ${secret.content}`
)
console.log(
colors.yellow(
'Suggestion: Replace with environment variables or secure storage solutions.\n'
)
)
})
console.log('=============================')
console.log('Summary:')
console.log('=============================')
console.log(`🚨 Total Secrets Found: ${foundSecrets.length}\n`)

process.exit(1)
} else {
console.log('=============================')
console.log('Summary:')
console.log('=============================')
console.log('✅ Total Secrets Found: 0\n')
console.log(colors.green('No hardcoded secrets found.'))
}
}

private getAllFiles(): string[] {
const currentWorkDir = process.cwd()
let gitIgnorePatterns: string[] = []
try {
const gitIgnorePath = path.resolve(currentWorkDir, '.gitignore')

const gitIgnoreContent = readFileSync(gitIgnorePath, 'utf8')

gitIgnorePatterns = gitIgnoreContent
.split('\n')
.filter((line) => line.trim() !== '' && !line.startsWith('#'))
} catch (error) {
console.log("Repository doesn't have .gitignore file")
}

return globSync(currentWorkDir + '/**/**', {
dot: true,
ignore: {
ignored: (p) => {
return gitIgnorePatterns.some((pattern) => {
return p.isNamed(pattern)
})
},
childrenIgnored: (p) => {
return gitIgnorePatterns.some((pattern) => {
return p.isNamed(pattern)
})
}
}
})
}

private getChangedFiles(): string[] {
const output = execSync('git status -s').toString()
const files = output
.split('\n')
.filter((line) => {
if (typeof line === 'undefined') {
return false
}
return line
})
.map((line) => {
line = line.trim().split(' ')[1]
return path.resolve(process.cwd(), line)
})
return files
}
}
4 changes: 3 additions & 1 deletion apps/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import InitCommand from './commands/init.command'
import RunCommand from './commands/run.command'
import ProfileCommand from './commands/profile.command'
import EnvironmentCommand from './commands/environment.command'
import ScanCommand from '@/commands/scan.command'

const program = new Command()

Expand All @@ -15,7 +16,8 @@ const COMMANDS: BaseCommand[] = [
new RunCommand(),
new InitCommand(),
new ProfileCommand(),
new EnvironmentCommand()
new EnvironmentCommand(),
new ScanCommand()
]

COMMANDS.forEach((command) => {
Expand Down
10 changes: 8 additions & 2 deletions apps/cli/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@
"verbose": false
},
"ts-node": {
"require": ["tsconfig-paths/register"]
"require": ["tsconfig-paths/register"],
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts"],
"include": ["src/**/*.ts", "tsup.config.ts"],
"exclude": ["node_modules"]
}
15 changes: 15 additions & 0 deletions apps/cli/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Options } from 'tsup'
import { defineConfig } from 'tsup'

const env = process.env.NODE_ENV

export default defineConfig((options: Options) => ({
plugins: [],
treeshake: true,
splitting: true,
entryPoints: ['src/index.ts'],
dts: true,
minify: env === 'production',
clean: true,
...options
}))
1 change: 1 addition & 0 deletions apps/platform/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ COPY --from=installer --chown=nextjs:nodejs /app/apps/platform/public ./apps/pl
ENV PORT=3025
ENV NEXT_SHARP_PATH=/app/apps/platform/.next/sharp
EXPOSE 3025
# keyshade-ignore
ENV HOSTNAME "0.0.0.0"


Expand Down
1 change: 1 addition & 0 deletions apps/web/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/pub
ENV PORT=3000
ENV NEXT_SHARP_PATH=/app/apps/web/.next/sharp
EXPOSE 3000
# keyshade-ignore
ENV HOSTNAME "0.0.0.0"


Expand Down
31 changes: 31 additions & 0 deletions packages/secret-scan/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module'
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'standard-with-typescript'
],
root: true,
env: {
node: true,
jest: true
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': ['warn'],
'@typescript-eslint/space-before-function-paren': 'off',
'@typescript-eslint/strict-boolean-expressions': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'space-before-function-paren': 'off',
}
}
5 changes: 5 additions & 0 deletions packages/secret-scan/.mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"require": "tsx",
"spec": "src/test/**/*.ts",
"extension": ["ts"]
}
28 changes: 28 additions & 0 deletions packages/secret-scan/.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": false,
"dynamicImport": true,
"decorators": false
},
"transform": null,
"target": "es2022",
"loose": false,
"externalHelpers": false,
"keepClassNames": false,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"module": {
"type": "commonjs",
"strict": false,
"strictMode": false,
"noInterop": false,
"lazy": false
},
"minify": true
}
30 changes: 30 additions & 0 deletions packages/secret-scan/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "secret-scan",
"version": "1.0.0",
"description": "Do static analysis of a string to find secrets",
"main": "dist/index.js",
"scripts": {
"genKey": "tsx src/generateKey.ts",
"build": "tsup",
"test": "mocha"
},
"publishConfig": {
"access": "public"
},
"exports": {
".": "./dist/index.js"
},
"dependencies": {
"mocha": "^10.6.0",
"randexp": "^0.5.3",
"tsx": "^4.16.2"
},
"devDependencies": {
"@types/mocha": "^10.0.7",
"@types/node": "^20.14.10",
"eslint-config-standard-with-typescript": "^43.0.1",
"jest": "^29.5.0",
"tsup": "^8.1.2",
"typescript": "^5.5.3"
}
}
Loading

0 comments on commit 85cb8ab

Please sign in to comment.