Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/semgrep-complete-diff-input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"layne": patch
---

Run Semgrep on complete selected HEAD files in diff-only mode, preserving syntax and enclosing context while filtering reported findings to changed lines.
2 changes: 1 addition & 1 deletion .github/workflows/ci-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '22'
- run: rm -f package-lock.json && npm install
- run: npm ci
- run: npm run build
- run: npm run lint
- run: npm run validate-config
Expand Down
15 changes: 14 additions & 1 deletion src/__tests__/dispatcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,27 @@ describe('dispatch()', () => {
}));
});

it('passes scanFiles and scanWorkspacePath to the semgrep adapter', async () => {
it('passes scanFiles and repoWorkspacePath to the semgrep adapter', async () => {
await dispatch(BASE);
expect(runSemgrep).toHaveBeenCalledWith(expect.objectContaining({
workspacePath: '/tmp/ws',
changedFiles: ['src/app.js', 'src/utils.js'],
}));
});

it('preserves full-file Semgrep context while Trufflehog scans projected hunks', async () => {
await dispatch({
...BASE,
scanContext: { ...BASE_SCAN_CONTEXT, mode: 'diff_only', scanWorkspacePath: '/tmp/ws/.layne/diff-only' },
});
expect(runSemgrep).toHaveBeenCalledWith(expect.objectContaining({
workspacePath: '/tmp/ws', changedFiles: BASE_SCAN_CONTEXT.scanFiles,
}));
expect(runTrufflehog).toHaveBeenCalledWith(expect.objectContaining({
workspacePath: '/tmp/ws/.layne/diff-only', changedFiles: BASE_SCAN_CONTEXT.scanFiles,
}));
});

it('returns an empty array when all adapters return no findings', async () => {
const findings = await dispatch(BASE);
expect(findings).toEqual([]);
Expand Down
3 changes: 2 additions & 1 deletion src/dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ export async function dispatch({ scanContext, changedLineRanges, owner, repo }:

const [trufflehogFindings, semgrepFindings, claudeFindings, spectreFindings, depDoctorFindings] = await Promise.all([
runTrufflehog({ workspacePath: scanWorkspacePath, changedFiles: eligibleFiles, toolConfig: scanConfig.trufflehog }),
runSemgrep({ workspacePath: scanWorkspacePath, changedFiles: eligibleFiles, toolConfig: scanConfig.semgrep }),
// Semgrep needs complete syntax; the worker filters findings to changed lines.
runSemgrep({ workspacePath: repoWorkspacePath, changedFiles: eligibleFiles, toolConfig: scanConfig.semgrep }),
runClaude({ workspacePath: repoWorkspacePath, changedFiles: eligibleFiles, changedLineRanges, promptFiles, toolConfig: scanConfig.claude }),
runSpectre({ workspacePath: repoWorkspacePath, changedFiles: eligibleFiles, changedLineRanges, promptFiles, toolConfig: scanConfig.spectre }),
runDepDoctor({ workspacePath: repoWorkspacePath, changedFiles: eligibleFiles, baseSha, toolConfig: scanConfig.depDoctor }),
Expand Down
4 changes: 4 additions & 0 deletions website/docs/scanners/semgrep.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Semgrep

In both scan modes, Semgrep parses complete selected HEAD files. In `diff_only`
mode, Layne filters findings to exact changed lines after scanning. This preserves
the syntax and enclosing context required by structural and dataflow rules.

<div style={{textAlign: 'center'}}>
<img src="/img/semgrep.png" alt="Semgrep" width="200" />
</div>
Expand Down
Loading