From 6855ddea6ab98f86d0fcce163da9c7d8eaa94f11 Mon Sep 17 00:00:00 2001 From: Agustin Groh Date: Thu, 5 Mar 2026 13:14:36 -0300 Subject: [PATCH 1/3] feat(scan):SP-3755 implement configurable scan path --- CHANGELOG.md | 4 ++ OVERVIEW.md | 1 + codescantask/app.input.ts | 3 ++ codescantask/services/scan.service.ts | 4 +- codescantask/task.json | 8 ++++ codescantask/tests/path-utils.test.ts | 67 +++++++++++++++++++++++++++ codescantask/utils/path.utils.ts | 56 ++++++++++++++++++++++ 7 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 codescantask/tests/path-utils.test.ts create mode 100644 codescantask/utils/path.utils.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fa05ae..d110455 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.7.0] - 2026-03-01 +### Added +- Added `scanPath` input to configure a relative path within the repository as the scan root (e.g., `src` or `packages/api`). Defaults to `.` (repository root). + ## [1.6.0] - 2026-02-10 ### Added - Added support for scan tuning parameters diff --git a/OVERVIEW.md b/OVERVIEW.md index a2f5da2..6c7c3fa 100644 --- a/OVERVIEW.md +++ b/OVERVIEW.md @@ -177,6 +177,7 @@ When the pipeline is manually triggered or runs on a schedule, the results are u | scanFiles | Enable or disable file and snippet scanning | Optional | `true` | | scanossSettings | Settings file to use for scanning. See the SCANOSS settings [documentation](https://scanoss.readthedocs.io/projects/scanoss-py/en/latest/#settings-file) | Optional | `true` | | settingsFilepath | Filepath of the SCANOSS settings to be used for scanning | Optional | `scanoss.json` | +| scanPath | Relative path within the repository to scan (e.g., "src" or "packages/api"). Must be relative, no parent directory references (..) allowed. | Optional | `.` | | debug | Enable debugging | Optional | `false` | | adoPat | ADO Personal Access Token | Optional | - | diff --git a/codescantask/app.input.ts b/codescantask/app.input.ts index 1467579..e593426 100644 --- a/codescantask/app.input.ts +++ b/codescantask/app.input.ts @@ -22,6 +22,9 @@ */ import tl = require('azure-pipelines-task-lib/task'); +import { validateScanPath } from './utils/path.utils'; + +export const SCAN_PATH = validateScanPath(tl.getInput('scanPath')); export const DEPENDENCIES_ENABLED = tl.getInput('dependenciesEnabled') === 'true'; export const DEPENDENCIES_SCOPE = tl.getInput('dependenciesScope'); export const DEPENDENCY_SCOPE_EXCLUDE = tl.getInput('dependenciesScopeExclude'); diff --git a/codescantask/services/scan.service.ts b/codescantask/services/scan.service.ts index b3fd3b1..4c9cef6 100644 --- a/codescantask/services/scan.service.ts +++ b/codescantask/services/scan.service.ts @@ -29,7 +29,7 @@ import { DEPENDENCIES_ENABLED, DEPENDENCIES_SCOPE, DEPENDENCY_SCOPE_EXCLUDE, DEPENDENCY_SCOPE_INCLUDE, EXECUTABLE, OUTPUT_FILEPATH, REPO_DIR, RUNTIME_CONTAINER, - SCAN_FILES, SCANOSS_SETTINGS, SETTINGS_FILE_PATH, SKIP_SNIPPETS + SCAN_FILES, SCAN_PATH, SCANOSS_SETTINGS, SETTINGS_FILE_PATH, SKIP_SNIPPETS } from '../app.input'; import { ScannerResults } from './result.interface'; import path from 'path'; @@ -297,7 +297,7 @@ export class ScanService { */ private async buildArgs(): Promise> { return ['run','-v',`${this.options.inputFilepath}:/scanoss`, - this.options.runtimeContainer, 'scan', '.', '--output', `./${OUTPUT_FILEPATH}`, + this.options.runtimeContainer, 'scan', SCAN_PATH, '--output', `./${OUTPUT_FILEPATH}`, ...this.buildDependenciesArgs(), ...await this.detectSBOM(), ...this.buildSnippetArgs(), diff --git a/codescantask/task.json b/codescantask/task.json index b38e0bd..167247f 100644 --- a/codescantask/task.json +++ b/codescantask/task.json @@ -198,6 +198,14 @@ "required": false, "helpMarkDown": "Project version (required if using project name)" }, + { + "name": "scanPath", + "type": "string", + "label": "Relative path within the repository to scan (e.g., \"src\" or \"packages/api\"). Must be relative, no parent directory references (..) allowed.", + "defaultValue": ".", + "required": false, + "helpMarkDown": "Relative path within the repository to scan (e.g., \"src\" or \"packages/api\"). Must be relative, no parent directory references (..) allowed." + }, { "name": "debug", "type": "boolean", diff --git a/codescantask/tests/path-utils.test.ts b/codescantask/tests/path-utils.test.ts new file mode 100644 index 0000000..0cf8994 --- /dev/null +++ b/codescantask/tests/path-utils.test.ts @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +/* + Copyright (c) 2026, SCANOSS + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import assert from 'assert'; +import { validateScanPath } from '../utils/path.utils'; + +describe('validateScanPath', function () { + + it('should return "." for undefined input', function () { + assert.strictEqual(validateScanPath(undefined), '.'); + }); + + it('should return "." for empty string input', function () { + assert.strictEqual(validateScanPath(''), '.'); + }); + + it('should accept valid relative paths', function () { + assert.strictEqual(validateScanPath('src'), 'src'); + assert.strictEqual(validateScanPath('packages/api'), 'packages/api'); + assert.strictEqual(validateScanPath('src-v2/api_server'), 'src-v2/api_server'); + }); + + it('should strip leading "./" for consistency', function () { + assert.strictEqual(validateScanPath('./src'), 'src'); + assert.strictEqual(validateScanPath('./packages/api'), 'packages/api'); + }); + + it('should reject absolute Unix paths', function () { + assert.strictEqual(validateScanPath('/etc/passwd'), '.'); + assert.strictEqual(validateScanPath('/usr/local/bin'), '.'); + }); + + it('should reject absolute Windows paths', function () { + assert.strictEqual(validateScanPath('C:\\Windows\\System32'), '.'); + assert.strictEqual(validateScanPath('D:\\Projects'), '.'); + }); + + it('should reject parent directory traversal', function () { + assert.strictEqual(validateScanPath('..'), '.'); + assert.strictEqual(validateScanPath('../../../etc'), '.'); + assert.strictEqual(validateScanPath('src/../../etc'), '.'); + }); + + it('should return "." for "." input', function () { + assert.strictEqual(validateScanPath('.'), '.'); + }); +}); \ No newline at end of file diff --git a/codescantask/utils/path.utils.ts b/codescantask/utils/path.utils.ts new file mode 100644 index 0000000..1e8517b --- /dev/null +++ b/codescantask/utils/path.utils.ts @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +/* + Copyright (c) 2026, SCANOSS + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import path = require('path'); + +/** + * Validates a scan path to prevent directory traversal and ensure safe operations. + * @param scanPath - The scan path to validate + * @returns A safe scan path or defaults to current directory + */ +export function validateScanPath(scanPath: string | undefined): string { + if (!scanPath) { + return '.'; + } + + // Normalize and convert to forward slashes for consistency + const normalizedPath = path.normalize(scanPath).replace(/\\/g, '/'); + + // Reject absolute paths (Unix-style and Windows-style) + const windowsAbsolutePattern = /^[a-zA-Z]:/; + if (path.isAbsolute(scanPath) || windowsAbsolutePattern.test(normalizedPath)) { + console.warn(`Absolute scan paths not allowed: ${scanPath}. Using default: .`); + return '.'; + } + + // Reject directory traversal attempts + if (normalizedPath.includes('..')) { + console.warn(`Invalid scan path detected: "${scanPath}". Using default: .`); + return '.'; + } + + // Remove leading './' for consistency + const cleaned = normalizedPath.startsWith('./') ? normalizedPath.slice(2) : normalizedPath; + + return cleaned || '.'; +} \ No newline at end of file From 2d8b5b649f2af499553c89f5bd761c32930761bd Mon Sep 17 00:00:00 2001 From: Agustin Groh Date: Thu, 5 Mar 2026 14:20:01 -0300 Subject: [PATCH 2/3] chore(runtime): upgrade scanoss.py runtime container to v1.4.6 --- CHANGELOG.md | 2 ++ OVERVIEW.md | 2 +- codescantask/app.input.ts | 2 +- codescantask/task.json | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d110455..3d15cce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.7.0] - 2026-03-01 ### Added - Added `scanPath` input to configure a relative path within the repository as the scan root (e.g., `src` or `packages/api`). Defaults to `.` (repository root). +### Changed +- Upgraded scanoss-py version to v1.46.0 ## [1.6.0] - 2026-02-10 ### Added diff --git a/OVERVIEW.md b/OVERVIEW.md index 6c7c3fa..b73e499 100644 --- a/OVERVIEW.md +++ b/OVERVIEW.md @@ -169,7 +169,7 @@ When the pipeline is manually triggered or runs on a schedule, the results are u | depTrackProjectVersion | Dependency Track project version (required if projectId not provided). | Optional | - | | apiUrl | SCANOSS API URL | Optional | `https://api.osskb.org/scan/direct` | | apiKey | SCANOSS API Key | Optional | - | -| runtimeContainer | Runtime URL | Optional | `ghcr.io/scanoss/scanoss-py:v1.45.0` | +| runtimeContainer | Runtime URL | Optional | `ghcr.io/scanoss/scanoss-py:v1.46.0` | | licensesCopyleftInclude | List of Copyleft licenses to append to the default list. Provide licenses as a comma-separated list. | Optional | - | | licensesCopyleftExclude | List of Copyleft licenses to remove from default list. Provide licenses as a comma-separated list. | Optional | - | | licensesCopyleftExplicit | Explicit list of Copyleft licenses to consider. Provide licenses as a comma-separated list. | Optional | - | diff --git a/codescantask/app.input.ts b/codescantask/app.input.ts index e593426..14eb22b 100644 --- a/codescantask/app.input.ts +++ b/codescantask/app.input.ts @@ -37,7 +37,7 @@ export const API_URL = tl.getInput('apiUrl'); export const OUTPUT_FILEPATH = tl.getInput('outputFilepath') || "scanoss-raw.json"; export const REPO_DIR = tl.getVariable('Build.Repository.LocalPath') || ''; // Get repository path export const POLICIES_HALT_ON_FAILURE = tl.getInput('policiesHaltOnFailure') === 'true'; -export const RUNTIME_CONTAINER = tl.getInput('runtimeContainer') || "ghcr.io/scanoss/scanoss-py:v1.45.0"; +export const RUNTIME_CONTAINER = tl.getInput('runtimeContainer') || "ghcr.io/scanoss/scanoss-py:v1.46.0"; export const SKIP_SNIPPETS = tl.getInput('skipSnippets') === 'true'; export const SCAN_FILES = tl.getInput('scanFiles') === 'true'; export const SCANOSS_SETTINGS = tl.getInput('scanossSettings') === 'true'; diff --git a/codescantask/task.json b/codescantask/task.json index 167247f..608e5ea 100644 --- a/codescantask/task.json +++ b/codescantask/task.json @@ -82,7 +82,7 @@ "name": "runtimeContainer", "type": "string", "label": "Runtime container", - "defaultValue": "ghcr.io/scanoss/scanoss-py:v1.45.0", + "defaultValue": "ghcr.io/scanoss/scanoss-py:v1.46.0", "required": false, "helpMarkDown": "Specify runtime container to perform the scan." }, From fa821b984ad95207a533db3bd2e0b7997d221e48 Mon Sep 17 00:00:00 2001 From: Agustin Groh Date: Thu, 5 Mar 2026 14:38:22 -0300 Subject: [PATCH 3/3] chore(version): upgrade version to v1.7.0 --- CHANGELOG.md | 5 +++-- codescantask/package-lock.json | 4 ++-- codescantask/package.json | 2 +- codescantask/task.json | 2 +- vss-extension-dev.json | 2 +- vss-extension.json | 2 +- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d15cce..351fcd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [1.7.0] - 2026-03-01 +## [1.7.0] - 2026-03-05 ### Added - Added `scanPath` input to configure a relative path within the repository as the scan root (e.g., `src` or `packages/api`). Defaults to `.` (repository root). ### Changed @@ -57,4 +57,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [1.3.0]: https://github.com/scanoss/ado-code-scan/compare/v1.2.0...v1.3.0 [1.4.0]: https://github.com/scanoss/ado-code-scan/compare/v1.3.0...v1.4.0 [1.5.0]: https://github.com/scanoss/ado-code-scan/compare/v1.4.0...v1.5.0 -[1.6.0]: https://github.com/scanoss/ado-code-scan/compare/v1.5.0...v1.6.0 \ No newline at end of file +[1.6.0]: https://github.com/scanoss/ado-code-scan/compare/v1.5.0...v1.6.0 +[1.7.0]: https://github.com/scanoss/ado-code-scan/compare/v1.6.0...v1.7.0 \ No newline at end of file diff --git a/codescantask/package-lock.json b/codescantask/package-lock.json index 74a7219..29a8550 100644 --- a/codescantask/package-lock.json +++ b/codescantask/package-lock.json @@ -1,12 +1,12 @@ { "name": "azure-devops-integration", - "version": "0.21.72", + "version": "1.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "azure-devops-integration", - "version": "0.21.72", + "version": "1.7.0", "license": "ISC", "dependencies": { "axios": "^1.7.2", diff --git a/codescantask/package.json b/codescantask/package.json index 88ed0c4..cf5045a 100644 --- a/codescantask/package.json +++ b/codescantask/package.json @@ -1,6 +1,6 @@ { "name": "azure-devops-integration", - "version": "1.6.0", + "version": "1.7.0", "description": "", "main": "index.js", "scripts": { diff --git a/codescantask/task.json b/codescantask/task.json index 608e5ea..a0076e3 100644 --- a/codescantask/task.json +++ b/codescantask/task.json @@ -9,7 +9,7 @@ "author": "SCANOSS", "version": { "Major": 1, - "Minor": 6, + "Minor": 7, "Patch": 0 }, "instanceNameFormat": "SCANOSS Code Scan", diff --git a/vss-extension-dev.json b/vss-extension-dev.json index 3914e5f..e1491c3 100644 --- a/vss-extension-dev.json +++ b/vss-extension-dev.json @@ -2,7 +2,7 @@ "manifestVersion": 1, "id": "scanoss-code-scan-dev", "name": "SCANOSS Code Scan DEV", - "version": "0.21.72", + "version": "1.7.0", "publisher": "SCANOSS", "public": false, "targets": [ diff --git a/vss-extension.json b/vss-extension.json index a3259d3..f925278 100644 --- a/vss-extension.json +++ b/vss-extension.json @@ -2,7 +2,7 @@ "manifestVersion": 1, "id": "scanoss-code-scan", "name": "SCANOSS Code Scan", - "version": "1.6.0", + "version": "1.7.0", "publisher": "SCANOSS", "public": true, "targets": [