forked from keyshade-xyz/keyshade
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cli): Secret scan (keyshade-xyz#438)
Co-authored-by: kriptonian1 <shymal.sawan@gmail.com>
- Loading branch information
1 parent
4f0ab2c
commit b0ea773
Showing
88 changed files
with
6,627 additions
and
1,801 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
})) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"require": "tsx", | ||
"spec": "src/test/**/*.ts", | ||
"extension": ["ts"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
Oops, something went wrong.