Repository navigation
App Security: scan multiple directories with --include-dir #8739
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -203,6 +203,11 @@ interface GatherInput { | |
| appDirectory: string | ||
| /** Absolute real paths of the directories to walk. */ | ||
| scanDirectories: ReadonlyArray<string> | ||
| /** | ||
| * Every directory that was asked for, including those inside another scan directory, so each can be checked for the | ||
| * ignored-scan-directory warning. It contains every entry of `scanDirectories`. | ||
| */ | ||
| requestedScanDirectories: ReadonlyArray<string> | ||
| /** Added after filtering, so no path rule can remove it. */ | ||
| selectedAppConfigFilePath?: string | ||
| rules: PathRules | ||
|
|
@@ -211,28 +216,36 @@ interface GatherInput { | |
| interface GatheredPaths { | ||
| /** Sorted and unique, relative to the app directory, written with `/`. They may start with `../`. */ | ||
| paths: string[] | ||
| /** Scan directories that their repository ignores, so only the files Git tracks in them were gathered. */ | ||
| /** Requested directories that their repository ignores, so only the files Git tracks in them are gathered. */ | ||
| ignoredScanDirectories: string[] | ||
| /** How the first scan directory's ignored paths were found. Secret findings use it to explain why an ignored file was scanned. */ | ||
| listingStatus: GatheredListingStatus | ||
| } | ||
|
|
||
| interface GatheredScanDirectory { | ||
| absolutePaths: string[] | ||
| ignored: boolean | ||
| listingStatus: GatheredPaths['listingStatus'] | ||
| } | ||
|
|
||
| export async function gatherPaths({ | ||
| appDirectory, | ||
| scanDirectories, | ||
| requestedScanDirectories, | ||
| selectedAppConfigFilePath, | ||
| rules, | ||
| }: GatherInput): Promise<GatheredPaths> { | ||
| const ignoredScanDirectories: string[] = [] | ||
| if (rules.gitFiltering) { | ||
| for (const requestedDirectory of requestedScanDirectories) { | ||
| // eslint-disable-next-line no-await-in-loop | ||
| if (await isIgnoredByParentRepository(requestedDirectory)) ignoredScanDirectories.push(requestedDirectory) | ||
| } | ||
| } | ||
|
|
||
| const gathered: GatheredScanDirectory[] = [] | ||
| for (const scanDirectory of scanDirectories) { | ||
| // eslint-disable-next-line no-await-in-loop | ||
| gathered.push(await gatherScanDirectory(scanDirectory, rules)) | ||
| gathered.push(await gatherScanDirectory(scanDirectory, rules, ignoredScanDirectories.includes(scanDirectory))) | ||
| } | ||
|
|
||
| const absolutePaths = [ | ||
|
|
@@ -241,21 +254,24 @@ export async function gatherPaths({ | |
| ] | ||
| return { | ||
| paths: [...new Set(absolutePaths.map((path) => normalizeCliPath(relativePath(appDirectory, path))))].sort(), | ||
| ignoredScanDirectories: scanDirectories.filter((_directory, index) => gathered[index]?.ignored), | ||
| ignoredScanDirectories, | ||
| listingStatus: gathered[0]?.listingStatus ?? (rules.gitFiltering ? 'tracked-only' : 'git-ignore-off'), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: the first directory's listing status explains every secret finding, including files from other directories.
Consider carrying the walk or repository status of the file's own scan directory to Static reading only; not reproduced at runtime. |
||
| } | ||
| } | ||
|
|
||
| async function gatherScanDirectory(scanDirectory: string, rules: PathRules): Promise<GatheredScanDirectory> { | ||
| async function gatherScanDirectory( | ||
| scanDirectory: string, | ||
| rules: PathRules, | ||
| ignoredByRepository: boolean, | ||
| ): Promise<GatheredScanDirectory> { | ||
| if (!rules.gitFiltering) { | ||
| return { | ||
| absolutePaths: await walkDirectory(scanDirectory, rules, undefined), | ||
| ignored: false, | ||
| listingStatus: 'git-ignore-off', | ||
| } | ||
| } | ||
|
|
||
| if (await isIgnoredByParentRepository(scanDirectory)) { | ||
| if (ignoredByRepository) { | ||
| const trackedPaths = await listTrackedFiles(scanDirectory) | ||
| // A normal walk would scan the untracked files that Git was told to ignore. | ||
| if (trackedPaths === undefined) { | ||
|
|
@@ -266,15 +282,13 @@ async function gatherScanDirectory(scanDirectory: string, rules: PathRules): Pro | |
| .filter((trackedPath) => !isDroppedTrackedPath(rules, scanDirectory, trackedPath)) | ||
| .map((trackedPath) => joinPath(scanDirectory, trackedPath)) | ||
| .filter(isTrackedFile), | ||
| ignored: true, | ||
| listingStatus: 'tracked-only', | ||
| } | ||
| } | ||
|
|
||
| const listing = await listGitIgnoredPaths(scanDirectory) | ||
| return { | ||
| absolutePaths: await walkDirectory(scanDirectory, rules, repositoryIgnoredPaths(scanDirectory, listing)), | ||
| ignored: false, | ||
| listingStatus: listing.status, | ||
| } | ||
| } | ||
|
|
@@ -483,15 +497,7 @@ type InspectedPath = {status: 'missing'} | {status: 'file'; path: string} | {sta | |
|
|
||
| function repositoryDisplayPath(path: string): string { | ||
| const relative = normalizeCliPath(relativePath(configuredReader().appDirectory, path)) | ||
| if ( | ||
| relative.length === 0 || | ||
| relative === '.' || | ||
| relative === '..' || | ||
| relative.startsWith('../') || | ||
| isAbsolutePath(relative) | ||
| ) { | ||
| return 'path' | ||
| } | ||
| if (relative.length === 0 || relative === '.' || isAbsolutePath(relative)) return 'path' | ||
| return relative | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: the remediation for a file in another repository assumes the app repository.
For a tracked
../backend/.env, this branch suggestsgit rm --cached ../backend/.envand adding that path to.gitignore. Git cannot remove a path outside the running repository's work tree, and the app's ignore rules cannot protect a sibling repository's file.The Git probes now correctly run in the file's own repository (
dirname(file.absolutePath)), but the evidence strings still read as app-root commands. Consider rendering the remediation and evidence with the owning repository's directory and the filename relative to it, while keeping the finding location app-relative.Presentation-only: no execution runs these commands. Static reading; not reproduced at runtime.