From a0ef30f803c820997f25c44b91ee3f9ea0584818 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 10 Mar 2026 09:58:44 +0000 Subject: [PATCH 01/30] changed go-ci.yml to use a script to check searchLine --- .../scripts/validate-search-line/EXAMPLES.md | 120 ++++++++++++ .../scripts/validate-search-line/README.md | 133 +++++++++++++ .../validate-search-line/requirements.txt | 2 + .../validate-search-line-lite.py | 172 +++++++++++++++++ .../validate-search-line.py | 175 ++++++++++++++++++ .github/workflows/go-ci.yml | 22 +++ .github/workflows/validate-search-line.yaml | 42 +++++ 7 files changed, 666 insertions(+) create mode 100644 .github/scripts/validate-search-line/EXAMPLES.md create mode 100644 .github/scripts/validate-search-line/README.md create mode 100644 .github/scripts/validate-search-line/requirements.txt create mode 100644 .github/scripts/validate-search-line/validate-search-line-lite.py create mode 100644 .github/scripts/validate-search-line/validate-search-line.py create mode 100644 .github/workflows/validate-search-line.yaml diff --git a/.github/scripts/validate-search-line/EXAMPLES.md b/.github/scripts/validate-search-line/EXAMPLES.md new file mode 100644 index 00000000000..53e78edb82f --- /dev/null +++ b/.github/scripts/validate-search-line/EXAMPLES.md @@ -0,0 +1,120 @@ +""" +Exemplos de como usar searchLine corretamente em queries KICS +""" + +# ===== EXEMPLO 1: Query com build_search_line ===== +# arquivo: assets/queries/cicd/example_query/query.rego + +""" +package Cx + +import data.generic.common as common_lib + +CxPolicy[result] { + # Procura um campo específico na configuração + config := input.document[i].jobs[j].steps[k].run + + # Verifica se contém padrão malicioso + contains(config, "dangerous") + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("run={{%s}}", [config]), + "issueType": "IncorrectValue", + "keyExpectedValue": "Run block is safe", + "keyActualValue": "Run block contains dangerous code", + # NÃO use -1! Use build_search_line com o path correto + "searchLine": common_lib.build_search_line(["jobs", j, "steps", k, "run"],[]), + "searchValue": config + } +} +""" + +# ===== EXEMPLO 2: Query sem build_search_line ===== +# (Menos recomendado, mas possível) + +""" +package Cx + +CxPolicy[result] { + # Se a structure é fixa e você quer retornar uma linha específica + resource := input.document[i].resources[name] + line := resource.line # Obtém a linha do campo + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.%s", [name]), + "issueType": "IncorrectValue", + # Se você tem acesso à linha, pode usá-la directamente + "searchLine": line, # ✅ VÁLIDO se line é a linha correcta + ... + } +} +""" + +# ===== EXEMPLO 3: Ficheiro de teste esperado ===== +# arquivo: assets/queries/cicd/example_query/test/positive_expected_result.json + +""" +[ + { + "queryName": "Example Query Name", + "severity": "HIGH", + "line": 5, + "searchLine": 5 # Opcional em expected_result.json, mas se presente, deve==line + } +] +""" + +# ===== EXEMPLO 4: build_search_line internamente ===== + +""" +A função common_lib.build_search_line() faz: + +1. Recebe um path: ["jobs", 0, "steps", 1, "run"] +2. Navega na estrutura JSON/YAML do documento +3. Encontra a linha (linha_do_arquivo) onde aquele field está +4. Retorna essa linha + +Exemplo com YAML: +jobs: # linha 1 + test: # linha 2 + steps: # linha 3 + - name: test # linha 4 + run: npm test # linha 5 ← searchLine retornará 5 + +build_search_line(["jobs", "test", "steps", 0, "run"], []) + → linha 5 +""" + +# ===== COMPARAÇÃO FINAL ===== + +""" +✅ CORRETO: +result := { + "line": 10, + "searchLine": 10, # Igual ao line + ... +} + +✅ TAMBÉM CORRETO: +result := { + "line": 10, + # Nenhum searchLine = será calculado automaticamente + ... +} + +❌ ERRADO: +result := { + "line": 10, + "searchLine": -1, # Não pode ser -1! + ... +} + +❌ ERRADO: +result := { + "line": 10, + "searchLine": 5, # Não corresponde a line! + ... +} +""" diff --git a/.github/scripts/validate-search-line/README.md b/.github/scripts/validate-search-line/README.md new file mode 100644 index 00000000000..cf51e7dc8cd --- /dev/null +++ b/.github/scripts/validate-search-line/README.md @@ -0,0 +1,133 @@ +# SearchLine Validation + +Esta GitHub Action valida automaticamente que o campo `searchLine` em resultados de queries está correctamente configurado. + +## 📋 O que valida? + +Para cada query modificada num PR, a ação: + +1. **Detecta queries modificadas** - Procura por ficheiros `query.rego` alterados em `assets/queries/` +2. **Valida searchLine** - Verifica que: + - `searchLine` NÃO é -1 (deve estar definida) + - Se `searchLine` não é -1, deve ser igual ao valor de `line` + +## 📁 Estrutura de uma Query + +``` +assets/queries//// +├── metadata.json # Metadados da query +├── query.rego # Implementação da query +└── test/ + ├── positive*.tf # Testes positivos (devem ser detectados) + ├── positive*.json # Resultados esperados + └── negative* # Testes negativos (não devem ser detectados) +``` + +## 🔧 Duas Versões Disponíveis + +### 1. **validate-search-line.yaml** (Completa) +- Compila KICS a partir do código fonte +- Executa as queries contra os ficheiros de teste +- Valida os resultados reais gerados + +**Triggers:** +```yaml +paths: + - "assets/queries/**/query.rego" +``` + +**Vantagens:** Teste mais rigoroso e realista +**Desvantagens:** Mais tempo de execução (compilação do Go) + +### 2. **validate-search-line-lite.yaml** (Rápida) +- Análise estática dos ficheiros Rego e JSON +- Sem compilação ou execução de queries +- Mais rápida, ideal para CI/CD rápido + +**Triggers:** +```yaml +paths: + - "assets/queries/**/query.rego" + - "assets/queries/**/test/*expected_result*.json" +``` + +**Vantagens:** Executa prontamente, feedback rápido +**Desvantagens:** Não valida comportamento real + +### 📌 Recomendação + +Use a versão **lite** para feedback rápido, e a versão **completa** para validação robusta antes de merge. + +## ✍️ Exemplos de searchLine + +### ❌ Evite a hardcoded -1 + +```rego +result := { + "searchLine": -1, // ❌ ERRADO! Causa erro de validação + ... +} +``` + +### ✅ Use build_search_line() + +```rego +result := { + "documentId": input.document[i].id, + "searchKey": sprintf("run={{%s}}", [run]), + "issueType": "IncorrectValue", + "searchLine": common_lib.build_search_line(["jobs", j, "steps", k, "run"],[]), + ... +} +``` + +## 📊 Valores Especiais + +- `searchLine: -1` → ❌ Causa erro (deve estar definida) +- `searchLine: ` → ✅ Deve ser igual a `line` + +## 🐛 Como Corrigir Erros + +### Erro: "searchLine é -1" +- Revise a chamada a `build_search_line()` na query +- Certifique-se que os índices do path estão correctos +- O path deve levar à linha exacta do recurso + +### Erro: "searchLine != line" +- O caminho passado a `build_search_line()` não está determinando a linha correcta +- Revise os índices do path da query +- Adicione debug mantendo os prints da query + +### Exemplo de Debug + +```rego +// Na query, imprima os índices para compreender a estrutura +print_debug := { + "doc_index": i, + "jobs_index": j, + "steps_index": k, +} +``` + +## 💻 Executar Localmente + +### Versão Lite +```bash +cd .github/scripts/validate-search-line/ +pip3 install requests +KICS_PR_NUMBER=123 KICS_GITHUB_TOKEN=your_token python3 validate-search-line-lite.py +``` + +### Versão Completa +```bash +cd .github/scripts/validate-search-line/ +pip3 install -r requirements.txt +make build # Compilar KICS +KICS_PR_NUMBER=123 KICS_GITHUB_TOKEN=your_token python3 validate-search-line.py +``` + +## 📚 Referências + +- [Rego Language Docs](https://www.openpolicyagent.org/docs/latest/policy-language/) +- [KICS Queries](https://docs.kics.io/latest/queries/) +- [Common Lib Functions](../../../assets/libraries/common.rego) diff --git a/.github/scripts/validate-search-line/requirements.txt b/.github/scripts/validate-search-line/requirements.txt new file mode 100644 index 00000000000..dd870f614de --- /dev/null +++ b/.github/scripts/validate-search-line/requirements.txt @@ -0,0 +1,2 @@ +# No external dependencies required. +# Changed files are provided by dorny/paths-filter via CHANGED_QUERIES env var. diff --git a/.github/scripts/validate-search-line/validate-search-line-lite.py b/.github/scripts/validate-search-line/validate-search-line-lite.py new file mode 100644 index 00000000000..dfeb597edc5 --- /dev/null +++ b/.github/scripts/validate-search-line/validate-search-line-lite.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +""" +Validador de searchLine para queries KICS (versao lite - analise estatica). +Recebe a lista de ficheiros alterados via CHANGED_QUERIES (JSON array do dorny/paths-filter). + +Validacoes: + 1. Se o query.rego tiver searchLine, nao pode estar hardcoded a -1 + 2. Nos expected results, se searchLine e line existirem: + - searchLine != -1 + - searchLine == line +""" + +import os +import re +import json +import sys +from pathlib import Path + +# O script corre a partir de .github/scripts/validate-search-line/ +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent + + +def exit_with_error(message): + print(f"::error::{message}") + sys.exit(1) + + +def get_changed_queries(): + """Obtem a lista de query.rego alterados via env CHANGED_QUERIES (JSON array do dorny/paths-filter).""" + changed = os.getenv('CHANGED_QUERIES', '') + if not changed: + exit_with_error("CHANGED_QUERIES environment variable is empty or not set") + + try: + files = json.loads(changed) + except json.JSONDecodeError: + exit_with_error(f"CHANGED_QUERIES is not valid JSON: {changed}") + + # Extrair directorios unicos das queries + query_dirs = [] + for f in files: + if f.endswith('/query.rego'): + query_dirs.append(REPO_ROOT / Path(f).parent) + + return query_dirs + + +def validate_rego_file(rego_file): + """Valida que o query.rego define searchLine correctamente. + + - Se searchLine nao existir, skip (nao e obrigatorio) + - Se existir, nao pode estar hardcoded a -1 + """ + try: + content = rego_file.read_text() + except Exception as e: + print(f" ::warning file={rego_file}::Cannot read file: {e}") + return True + + if 'searchLine' not in content: + print(f" [SKIP] searchLine not found in {rego_file.name}") + return True + + # Detectar searchLine hardcoded a -1 + if re.search(r'"searchLine"\s*:\s*-1', content): + print(f" ::error file={rego_file.relative_to(REPO_ROOT)}::searchLine is hardcoded to -1") + return False + + # Verificar se usa build_search_line() (padrao recomendado) + if 'build_search_line' in content: + print(f" [OK] {rego_file.name} uses build_search_line()") + return True + + # Verificar se searchLine e atribuido a uma variavel + if re.search(r'"searchLine"\s*:\s*[a-zA-Z_]\w*', content): + print(f" [OK] {rego_file.name} has searchLine assigned to a variable") + return True + + print(f" ::warning file={rego_file.relative_to(REPO_ROOT)}::searchLine pattern unclear - review manually") + return True + + +def validate_expected_results(query_dir): + """Valida os ficheiros de expected results. + + Para cada resultado esperado que contenha searchLine e line: + - searchLine != -1 + - searchLine == line + """ + test_dir = query_dir / 'test' + + if not test_dir.exists(): + print(f" [SKIP] No test directory found") + return True + + expected_files = list(test_dir.glob('*expected_result*.json')) + + if not expected_files: + print(f" [SKIP] No expected result files found") + return True + + all_valid = True + for expected_file in expected_files: + try: + results = json.loads(expected_file.read_text()) + except json.JSONDecodeError: + print(f" ::warning file={expected_file.relative_to(REPO_ROOT)}::Invalid JSON") + continue + except Exception as e: + print(f" ::warning file={expected_file.relative_to(REPO_ROOT)}::Error reading: {e}") + continue + + if not isinstance(results, list): + results = [results] + + for idx, result in enumerate(results): + search_line = result.get('searchLine') + line = result.get('line') + + if search_line is None or line is None: + continue + + rel_path = expected_file.relative_to(REPO_ROOT) + + if search_line == -1: + print(f" ::error file={rel_path}::Result [{idx}]: searchLine is -1 (line={line})") + all_valid = False + elif search_line != line: + print(f" ::error file={rel_path}::Result [{idx}]: searchLine ({search_line}) != line ({line})") + all_valid = False + else: + print(f" [OK] {rel_path}: result [{idx}] searchLine={search_line} matches line") + + return all_valid + + +def main(): + print("Starting searchLine validation (lite mode)...\n") + + # Obter queries alteradas do dorny/paths-filter + modified_queries = get_changed_queries() + + if not modified_queries: + print("No query.rego files found in CHANGED_QUERIES - nothing to validate") + sys.exit(0) + + print(f"Found {len(modified_queries)} modified queries to validate:\n") + + all_valid = True + for query_dir in modified_queries: + rel_dir = query_dir.relative_to(REPO_ROOT) + print(f"--- Validating: {rel_dir}") + + rego_file = query_dir / 'query.rego' + if rego_file.exists(): + if not validate_rego_file(rego_file): + all_valid = False + + if not validate_expected_results(query_dir): + all_valid = False + + print() + + if all_valid: + print("All searchLine validations passed!") + sys.exit(0) + else: + exit_with_error("Some searchLine validations failed. See errors above.") + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/validate-search-line/validate-search-line.py b/.github/scripts/validate-search-line/validate-search-line.py new file mode 100644 index 00000000000..3b85983b1d8 --- /dev/null +++ b/.github/scripts/validate-search-line/validate-search-line.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 + +import os +import requests +import json +import subprocess +import sys +from pathlib import Path + +KICS_PR_NUMBER = os.getenv('KICS_PR_NUMBER') +KICS_GITHUB_TOKEN = os.getenv('KICS_GITHUB_TOKEN') + +def exit_with_error(message): + print(f"::error::{message}") + sys.exit(1) + +def exit_success(): + print("All searchLine validations passed!") + sys.exit(0) + +def fetch(page=1, max_items=100): + """Fetch PR files from GitHub API""" + print(f'Fetching PR #{KICS_PR_NUMBER} files... page {page}') + headers = {'Authorization': f'token {KICS_GITHUB_TOKEN}'} + url = f'https://api.github.com/repos/checkmarx/kics/pulls/{KICS_PR_NUMBER}/files?per_page={max_items}&page={page}' + response = requests.get(url, headers=headers) + return {"data": response.json(), "status": response.status_code} + +def fetch_pr_files(): + """Fetch all files modified in the PR""" + files = [] + page = 1 + max_items = 100 + + while page < 50: + response = fetch(page, max_items) + if response['status'] != 200: + return exit_with_error(f'Failed to fetch PR files\n- status code: {response["status"]}') + + for obj in response['data']: + if obj['status'] != 'removed': + files.append(obj['filename']) + + if len(response['data']) < max_items: + return files + + page += 1 + + return exit_with_error('Failed to fetch PR files - too many pages') + +def find_modified_queries(files): + """Find modified query files (query.rego files)""" + modified_queries = [] + for file in files: + # Match patterns like assets/queries/terraform/*/query.rego + if file.startswith('assets/queries/') and file.endswith('/query.rego'): + query_dir = str(Path(file).parent) + modified_queries.append(query_dir) + + return modified_queries + +def find_test_fixtures(query_dir): + """Find test fixtures for a query""" + test_dir = Path(query_dir) / 'test' + if not test_dir.exists(): + return [] + + fixtures = [] + for item in test_dir.glob('positive*.json'): + fixtures.append(str(item)) + + return fixtures + +def validate_query_results(query_dir): + """Run KICS on test files and validate searchLine""" + print(f"\n🔍 Validating query: {query_dir}") + + test_dir = Path(query_dir) / 'test' + if not test_dir.exists(): + print(f"No test directory found") + return True + + test_files = list(test_dir.glob('positive*')) + list(test_dir.glob('negative*')) + if not test_files: + print(f"No test files found") + return True + + # Run KICS on test directory using the compiled binary + kics_binary = './bin/kics' + cmd = [ + kics_binary, 'scan', + '-p', str(test_dir), + '--queries', str(query_dir), + '-o', '/tmp/kics-result.json', + '--output-formats', 'json', + '--exclude-paths', 'test' + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, cwd='../..') + if result.returncode not in [0, 20, 40, 50]: # KICS returns different codes for different scenarios + print(f"KICS scan returned code {result.returncode}") + if result.stderr: + print(f" stderr: {result.stderr[:200]}") + return True # Don't fail the validation if KICS itself fails + except FileNotFoundError: + print(f"KICS binary not found at {kics_binary}") + return True + except Exception as e: + print(f"Failed to run KICS: {e}") + return True + + # Validate results + try: + with open('/tmp/kics-result.json', 'r') as f: + results = json.load(f) + except: + print(f"No results file generated") + return True + + all_valid = True + results_list = results.get('results', []) + + if not results_list: + print(f"No issues found in test files") + return True + + for result in results_list: + search_line = result.get('searchLine', -1) + line = result.get('line', -1) + + # Validate searchLine + if search_line == -1: + print(f"searchLine is -1 for {result.get('fileName')}:{line}") + all_valid = False + elif search_line != line: + print(f"searchLine ({search_line}) != line ({line}) in {result.get('fileName')}") + all_valid = False + else: + print(f"{result.get('fileName')}:{line} - searchLine correctly set to {search_line}") + + return all_valid + +def main(): + print("Starting searchLine validation...\n") + + if not KICS_PR_NUMBER or not KICS_GITHUB_TOKEN: + exit_with_error("Missing KICS_PR_NUMBER or KICS_GITHUB_TOKEN environment variables") + + # Fetch modified files + pr_files = fetch_pr_files() + print(f"Found {len(pr_files)} modified files in PR\n") + + # Find modified queries + modified_queries = find_modified_queries(pr_files) + + if not modified_queries: + print("No modified queries found in this PR") + exit_success() + + print(f"Found {len(modified_queries)} modified queries\n") + + # Validate each query + all_valid = True + for query_dir in modified_queries: + if not validate_query_results(query_dir): + all_valid = False + + if all_valid: + exit_success() + else: + exit_with_error("Some searchLine validations failed. Please fix the issues above.") + +if __name__ == "__main__": + main() diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index e9029c36282..6f5049537a9 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -90,6 +90,28 @@ jobs: with: name: unit-test-${{ runner.os }}-${{ github.event.pull_request.head.sha }}.log path: unit-test.log + validate-search-line: + name: validate-search-line + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Detect changed query.rego files + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + list-files: json + filters: | + queries: + - 'assets/queries/**/query.rego' + - name: Validate searchLine in modified queries + if: steps.filter.outputs.queries == 'true' + env: + CHANGED_QUERIES: ${{ steps.filter.outputs.queries_files }} + working-directory: .github/scripts/validate-search-line/ + run: python3 validate-search-line-lite.py security-scan: name: security-scan runs-on: ubuntu-latest diff --git a/.github/workflows/validate-search-line.yaml b/.github/workflows/validate-search-line.yaml new file mode 100644 index 00000000000..3ffa7e115d7 --- /dev/null +++ b/.github/workflows/validate-search-line.yaml @@ -0,0 +1,42 @@ +name: validate-search-line + +on: + pull_request: + branches: [master] + paths: + - "assets/queries/**/query.rego" + +jobs: + validate-search-line: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@0b93645e9e0d6c9823c012979046888a936bb523 # v5.0.0 + with: + python-version: "3.11" + + - name: Set up Go + uses: actions/setup-go@0a12ed9d6470c34f1fac053dbb1c47566dae491f # v5.0.0 + with: + go-version: "1.21" + cache: true + + - name: Build KICS + run: | + make build + working-directory: . + + - name: Run searchLine validation script + env: + KICS_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + KICS_PR_NUMBER: ${{ github.event.number }} + id: validator + working-directory: .github/scripts/validate-search-line/ + run: | + pip3 install -r requirements.txt + python3 validate-search-line.py From bd0d68808f32dc48f1f4f7b385c4c1952bec9f08 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 10 Mar 2026 10:06:32 +0000 Subject: [PATCH 02/30] . --- .../scripts/validate-search-line/EXAMPLES.md | 120 ---------------- .../scripts/validate-search-line/README.md | 133 ------------------ 2 files changed, 253 deletions(-) delete mode 100644 .github/scripts/validate-search-line/EXAMPLES.md delete mode 100644 .github/scripts/validate-search-line/README.md diff --git a/.github/scripts/validate-search-line/EXAMPLES.md b/.github/scripts/validate-search-line/EXAMPLES.md deleted file mode 100644 index 53e78edb82f..00000000000 --- a/.github/scripts/validate-search-line/EXAMPLES.md +++ /dev/null @@ -1,120 +0,0 @@ -""" -Exemplos de como usar searchLine corretamente em queries KICS -""" - -# ===== EXEMPLO 1: Query com build_search_line ===== -# arquivo: assets/queries/cicd/example_query/query.rego - -""" -package Cx - -import data.generic.common as common_lib - -CxPolicy[result] { - # Procura um campo específico na configuração - config := input.document[i].jobs[j].steps[k].run - - # Verifica se contém padrão malicioso - contains(config, "dangerous") - - result := { - "documentId": input.document[i].id, - "searchKey": sprintf("run={{%s}}", [config]), - "issueType": "IncorrectValue", - "keyExpectedValue": "Run block is safe", - "keyActualValue": "Run block contains dangerous code", - # NÃO use -1! Use build_search_line com o path correto - "searchLine": common_lib.build_search_line(["jobs", j, "steps", k, "run"],[]), - "searchValue": config - } -} -""" - -# ===== EXEMPLO 2: Query sem build_search_line ===== -# (Menos recomendado, mas possível) - -""" -package Cx - -CxPolicy[result] { - # Se a structure é fixa e você quer retornar uma linha específica - resource := input.document[i].resources[name] - line := resource.line # Obtém a linha do campo - - result := { - "documentId": input.document[i].id, - "searchKey": sprintf("resource.%s", [name]), - "issueType": "IncorrectValue", - # Se você tem acesso à linha, pode usá-la directamente - "searchLine": line, # ✅ VÁLIDO se line é a linha correcta - ... - } -} -""" - -# ===== EXEMPLO 3: Ficheiro de teste esperado ===== -# arquivo: assets/queries/cicd/example_query/test/positive_expected_result.json - -""" -[ - { - "queryName": "Example Query Name", - "severity": "HIGH", - "line": 5, - "searchLine": 5 # Opcional em expected_result.json, mas se presente, deve==line - } -] -""" - -# ===== EXEMPLO 4: build_search_line internamente ===== - -""" -A função common_lib.build_search_line() faz: - -1. Recebe um path: ["jobs", 0, "steps", 1, "run"] -2. Navega na estrutura JSON/YAML do documento -3. Encontra a linha (linha_do_arquivo) onde aquele field está -4. Retorna essa linha - -Exemplo com YAML: -jobs: # linha 1 - test: # linha 2 - steps: # linha 3 - - name: test # linha 4 - run: npm test # linha 5 ← searchLine retornará 5 - -build_search_line(["jobs", "test", "steps", 0, "run"], []) - → linha 5 -""" - -# ===== COMPARAÇÃO FINAL ===== - -""" -✅ CORRETO: -result := { - "line": 10, - "searchLine": 10, # Igual ao line - ... -} - -✅ TAMBÉM CORRETO: -result := { - "line": 10, - # Nenhum searchLine = será calculado automaticamente - ... -} - -❌ ERRADO: -result := { - "line": 10, - "searchLine": -1, # Não pode ser -1! - ... -} - -❌ ERRADO: -result := { - "line": 10, - "searchLine": 5, # Não corresponde a line! - ... -} -""" diff --git a/.github/scripts/validate-search-line/README.md b/.github/scripts/validate-search-line/README.md deleted file mode 100644 index cf51e7dc8cd..00000000000 --- a/.github/scripts/validate-search-line/README.md +++ /dev/null @@ -1,133 +0,0 @@ -# SearchLine Validation - -Esta GitHub Action valida automaticamente que o campo `searchLine` em resultados de queries está correctamente configurado. - -## 📋 O que valida? - -Para cada query modificada num PR, a ação: - -1. **Detecta queries modificadas** - Procura por ficheiros `query.rego` alterados em `assets/queries/` -2. **Valida searchLine** - Verifica que: - - `searchLine` NÃO é -1 (deve estar definida) - - Se `searchLine` não é -1, deve ser igual ao valor de `line` - -## 📁 Estrutura de uma Query - -``` -assets/queries//// -├── metadata.json # Metadados da query -├── query.rego # Implementação da query -└── test/ - ├── positive*.tf # Testes positivos (devem ser detectados) - ├── positive*.json # Resultados esperados - └── negative* # Testes negativos (não devem ser detectados) -``` - -## 🔧 Duas Versões Disponíveis - -### 1. **validate-search-line.yaml** (Completa) -- Compila KICS a partir do código fonte -- Executa as queries contra os ficheiros de teste -- Valida os resultados reais gerados - -**Triggers:** -```yaml -paths: - - "assets/queries/**/query.rego" -``` - -**Vantagens:** Teste mais rigoroso e realista -**Desvantagens:** Mais tempo de execução (compilação do Go) - -### 2. **validate-search-line-lite.yaml** (Rápida) -- Análise estática dos ficheiros Rego e JSON -- Sem compilação ou execução de queries -- Mais rápida, ideal para CI/CD rápido - -**Triggers:** -```yaml -paths: - - "assets/queries/**/query.rego" - - "assets/queries/**/test/*expected_result*.json" -``` - -**Vantagens:** Executa prontamente, feedback rápido -**Desvantagens:** Não valida comportamento real - -### 📌 Recomendação - -Use a versão **lite** para feedback rápido, e a versão **completa** para validação robusta antes de merge. - -## ✍️ Exemplos de searchLine - -### ❌ Evite a hardcoded -1 - -```rego -result := { - "searchLine": -1, // ❌ ERRADO! Causa erro de validação - ... -} -``` - -### ✅ Use build_search_line() - -```rego -result := { - "documentId": input.document[i].id, - "searchKey": sprintf("run={{%s}}", [run]), - "issueType": "IncorrectValue", - "searchLine": common_lib.build_search_line(["jobs", j, "steps", k, "run"],[]), - ... -} -``` - -## 📊 Valores Especiais - -- `searchLine: -1` → ❌ Causa erro (deve estar definida) -- `searchLine: ` → ✅ Deve ser igual a `line` - -## 🐛 Como Corrigir Erros - -### Erro: "searchLine é -1" -- Revise a chamada a `build_search_line()` na query -- Certifique-se que os índices do path estão correctos -- O path deve levar à linha exacta do recurso - -### Erro: "searchLine != line" -- O caminho passado a `build_search_line()` não está determinando a linha correcta -- Revise os índices do path da query -- Adicione debug mantendo os prints da query - -### Exemplo de Debug - -```rego -// Na query, imprima os índices para compreender a estrutura -print_debug := { - "doc_index": i, - "jobs_index": j, - "steps_index": k, -} -``` - -## 💻 Executar Localmente - -### Versão Lite -```bash -cd .github/scripts/validate-search-line/ -pip3 install requests -KICS_PR_NUMBER=123 KICS_GITHUB_TOKEN=your_token python3 validate-search-line-lite.py -``` - -### Versão Completa -```bash -cd .github/scripts/validate-search-line/ -pip3 install -r requirements.txt -make build # Compilar KICS -KICS_PR_NUMBER=123 KICS_GITHUB_TOKEN=your_token python3 validate-search-line.py -``` - -## 📚 Referências - -- [Rego Language Docs](https://www.openpolicyagent.org/docs/latest/policy-language/) -- [KICS Queries](https://docs.kics.io/latest/queries/) -- [Common Lib Functions](../../../assets/libraries/common.rego) From 823d88c40e254dd7e18a2fd07a1fc16e16183412 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:44:10 +0000 Subject: [PATCH 03/30] fixed print to not use f when is missing placeholders --- .../validate-search-line-lite.py | 14 +++++++------- .../validate-search-line/validate-search-line.py | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/scripts/validate-search-line/validate-search-line-lite.py b/.github/scripts/validate-search-line/validate-search-line-lite.py index dfeb597edc5..00a5151b3a1 100644 --- a/.github/scripts/validate-search-line/validate-search-line-lite.py +++ b/.github/scripts/validate-search-line/validate-search-line-lite.py @@ -90,13 +90,13 @@ def validate_expected_results(query_dir): test_dir = query_dir / 'test' if not test_dir.exists(): - print(f" [SKIP] No test directory found") + print(" [SKIP] No test directory found") return True expected_files = list(test_dir.glob('*expected_result*.json')) if not expected_files: - print(f" [SKIP] No expected result files found") + print(" [SKIP] No expected result files found") return True all_valid = True @@ -104,10 +104,10 @@ def validate_expected_results(query_dir): try: results = json.loads(expected_file.read_text()) except json.JSONDecodeError: - print(f" ::warning file={expected_file.relative_to(REPO_ROOT)}::Invalid JSON") + print(" ::warning file={expected_file.relative_to(REPO_ROOT)}::Invalid JSON") continue except Exception as e: - print(f" ::warning file={expected_file.relative_to(REPO_ROOT)}::Error reading: {e}") + print(" ::warning file={expected_file.relative_to(REPO_ROOT)}::Error reading: {e}") continue if not isinstance(results, list): @@ -123,13 +123,13 @@ def validate_expected_results(query_dir): rel_path = expected_file.relative_to(REPO_ROOT) if search_line == -1: - print(f" ::error file={rel_path}::Result [{idx}]: searchLine is -1 (line={line})") + print(" ::error file={rel_path}::Result [{idx}]: searchLine is -1 (line={line})") all_valid = False elif search_line != line: - print(f" ::error file={rel_path}::Result [{idx}]: searchLine ({search_line}) != line ({line})") + print(" ::error file={rel_path}::Result [{idx}]: searchLine ({search_line}) != line ({line})") all_valid = False else: - print(f" [OK] {rel_path}: result [{idx}] searchLine={search_line} matches line") + print(" [OK] {rel_path}: result [{idx}] searchLine={search_line} matches line") return all_valid diff --git a/.github/scripts/validate-search-line/validate-search-line.py b/.github/scripts/validate-search-line/validate-search-line.py index 3b85983b1d8..7cb6f52864d 100644 --- a/.github/scripts/validate-search-line/validate-search-line.py +++ b/.github/scripts/validate-search-line/validate-search-line.py @@ -77,12 +77,12 @@ def validate_query_results(query_dir): test_dir = Path(query_dir) / 'test' if not test_dir.exists(): - print(f"No test directory found") + print("No test directory found") return True test_files = list(test_dir.glob('positive*')) + list(test_dir.glob('negative*')) if not test_files: - print(f"No test files found") + print("No test files found") return True # Run KICS on test directory using the compiled binary @@ -115,14 +115,14 @@ def validate_query_results(query_dir): with open('/tmp/kics-result.json', 'r') as f: results = json.load(f) except: - print(f"No results file generated") + print("No results file generated") return True all_valid = True results_list = results.get('results', []) if not results_list: - print(f"No issues found in test files") + print("No issues found in test files") return True for result in results_list: From 6d644706d590cd37d02df97391c6f2b38df5f589 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:04:07 +0000 Subject: [PATCH 04/30] removed unnecessary action --- .github/workflows/go-ci.yml | 18 ++++++++- .github/workflows/validate-search-line.yaml | 42 --------------------- 2 files changed, 16 insertions(+), 44 deletions(-) delete mode 100644 .github/workflows/validate-search-line.yaml diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 6f5049537a9..a999562be0c 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -98,6 +98,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false + fetch-depth: 0 - name: Detect changed query.rego files uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 id: filter @@ -106,12 +107,25 @@ jobs: filters: | queries: - 'assets/queries/**/query.rego' + - name: Set up Python + if: steps.filter.outputs.queries == 'true' + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.13' + - name: Set up Go + if: steps.filter.outputs.queries == 'true' + run: make build - name: Validate searchLine in modified queries if: steps.filter.outputs.queries == 'true' env: CHANGED_QUERIES: ${{ steps.filter.outputs.queries_files }} - working-directory: .github/scripts/validate-search-line/ - run: python3 validate-search-line-lite.py + KICS_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + KICS_PR_NUMBER: ${{ github.event.number }} + working-directory: .gitu«hub/scripts/validate-search-line/ + run: | + pip3 install -r requirements.txt + python3 validate-search-line.py + security-scan: name: security-scan runs-on: ubuntu-latest diff --git a/.github/workflows/validate-search-line.yaml b/.github/workflows/validate-search-line.yaml deleted file mode 100644 index 3ffa7e115d7..00000000000 --- a/.github/workflows/validate-search-line.yaml +++ /dev/null @@ -1,42 +0,0 @@ -name: validate-search-line - -on: - pull_request: - branches: [master] - paths: - - "assets/queries/**/query.rego" - -jobs: - validate-search-line: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@0b93645e9e0d6c9823c012979046888a936bb523 # v5.0.0 - with: - python-version: "3.11" - - - name: Set up Go - uses: actions/setup-go@0a12ed9d6470c34f1fac053dbb1c47566dae491f # v5.0.0 - with: - go-version: "1.21" - cache: true - - - name: Build KICS - run: | - make build - working-directory: . - - - name: Run searchLine validation script - env: - KICS_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - KICS_PR_NUMBER: ${{ github.event.number }} - id: validator - working-directory: .github/scripts/validate-search-line/ - run: | - pip3 install -r requirements.txt - python3 validate-search-line.py From b8986d7c71e2020d7ec0beca9cf6bb9a151b3c7d Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:08:27 +0000 Subject: [PATCH 05/30] fixed typo --- .github/workflows/go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index a999562be0c..a0a95405f63 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -121,7 +121,7 @@ jobs: CHANGED_QUERIES: ${{ steps.filter.outputs.queries_files }} KICS_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} KICS_PR_NUMBER: ${{ github.event.number }} - working-directory: .gitu«hub/scripts/validate-search-line/ + working-directory: .github/scripts/validate-search-line/ run: | pip3 install -r requirements.txt python3 validate-search-line.py From 80400873ce8949867033112e9d1cc93c1940b32e Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:18:59 +0000 Subject: [PATCH 06/30] fixing issues from codacy --- .../validate-search-line/validate-search-line-lite.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/scripts/validate-search-line/validate-search-line-lite.py b/.github/scripts/validate-search-line/validate-search-line-lite.py index 00a5151b3a1..cf5c45a8991 100644 --- a/.github/scripts/validate-search-line/validate-search-line-lite.py +++ b/.github/scripts/validate-search-line/validate-search-line-lite.py @@ -107,7 +107,7 @@ def validate_expected_results(query_dir): print(" ::warning file={expected_file.relative_to(REPO_ROOT)}::Invalid JSON") continue except Exception as e: - print(" ::warning file={expected_file.relative_to(REPO_ROOT)}::Error reading: {e}") + print(f" ::warning file={expected_file.relative_to(REPO_ROOT)}::Error reading: {e}") continue if not isinstance(results, list): @@ -123,13 +123,13 @@ def validate_expected_results(query_dir): rel_path = expected_file.relative_to(REPO_ROOT) if search_line == -1: - print(" ::error file={rel_path}::Result [{idx}]: searchLine is -1 (line={line})") + print(f" ::error file={rel_path}::Result [{idx}]: searchLine is -1 (line={line})") all_valid = False elif search_line != line: - print(" ::error file={rel_path}::Result [{idx}]: searchLine ({search_line}) != line ({line})") + print(f" ::error file={rel_path}::Result [{idx}]: searchLine ({search_line}) != line ({line})") all_valid = False else: - print(" [OK] {rel_path}: result [{idx}] searchLine={search_line} matches line") + print(f" [OK] {rel_path}: result [{idx}] searchLine={search_line} matches line") return all_valid From 2b517c5eb9e0731ce91502bf8e00f71a8f8cca0a Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:30:03 +0000 Subject: [PATCH 07/30] updated go image in Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a9ce17650fe..10560cad34d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM checkmarx/go:1.26.0-r0@sha256:bbc945863cdee21f4bab2e80b4bd481cfee5c13ece8e576136bc478a5f6ad34d AS build_env +FROM checkmarx/go:1.26.1-r0@sha256:ce13f12ff5c4114de1df95b2442911adab6c5a3ee580945176213f78c94ca0c6 AS build_env # Copy the source from the current directory to the Working Directory inside the container WORKDIR /app From e18bee36044c89995966e57e370ba9420abf16a4 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:36:50 +0000 Subject: [PATCH 08/30] update go images --- docker/Dockerfile.alpine | 2 +- docker/Dockerfile.debian | 2 +- docker/Dockerfile.ubi8 | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index 74659e3d16a..47caa36a8ea 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -1,4 +1,4 @@ -FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.25.7-alpine AS build_env +FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.25.8-alpine AS build_env # Install build dependencies RUN apk add --no-cache git diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index db9f7bd30c8..f74b5517dd8 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -3,7 +3,7 @@ # it does not define an ENTRYPOINT as this is a requirement described here: # https://docs.microsoft.com/en-us/azure/devops/pipelines/process/container-phases?view=azure-devops#linux-based-containers # -FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.25.7-bookworm as build_env +FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.25.8-bookworm as build_env # Create a group and user RUN groupadd checkmarx && useradd -g checkmarx -M -s /bin/bash checkmarx USER checkmarx diff --git a/docker/Dockerfile.ubi8 b/docker/Dockerfile.ubi8 index e9caa31353f..85a39dc998e 100644 --- a/docker/Dockerfile.ubi8 +++ b/docker/Dockerfile.ubi8 @@ -4,10 +4,10 @@ WORKDIR /build ENV PATH=$PATH:/usr/local/go/bin -ADD https://golang.org/dl/go1.25.7.linux-amd64.tar.gz . +ADD https://golang.org/dl/go1.25.8.linux-amd64.tar.gz . RUN yum install git gcc -y \ - && rm -rf /usr/local/go && tar -C /usr/local -xzf go1.25.7.linux-amd64.tar.gz \ - && rm -f go1.25.7.linux-amd64.tar.gz + && rm -rf /usr/local/go && tar -C /usr/local -xzf go1.25.8.linux-amd64.tar.gz \ + && rm -f go1.25.8.linux-amd64.tar.gz ENV GOPRIVATE=github.com/Checkmarx/* ARG VERSION="development" From 2312cdac930c8eda39cae43916ff7748d131c51f Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:52:50 +0000 Subject: [PATCH 09/30] changed git image --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 10560cad34d..b96a62f5ac6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \ # Runtime image # Ignore no User Cmd since KICS container is stopped afer scan # kics-scan ignore-line -FROM checkmarx/git:2.53.0-r0@sha256:f46c18d1ae724ca35faa4884289e8203294e52cafb17717e3875ab2c636a0a7e +FROM checkmarx/git:2.53.0-r0@sha256:550d658abf13b060746bcc629daaa6eaf6ee66364e5b5fa6216eb92f5e4d0f97 ENV TERM xterm-256color From 09a2cb0d16a73d384f59abe3bc1afde373fe9e53 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 10 Mar 2026 16:28:43 +0000 Subject: [PATCH 10/30] changing positive expected results --- .../aks_private_cluster_disabled/query.rego | 1 + .../test/positive_expected_result.json | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/assets/queries/terraform/azure/aks_private_cluster_disabled/query.rego b/assets/queries/terraform/azure/aks_private_cluster_disabled/query.rego index 7bc9c6c6b62..eda41b0c2a3 100644 --- a/assets/queries/terraform/azure/aks_private_cluster_disabled/query.rego +++ b/assets/queries/terraform/azure/aks_private_cluster_disabled/query.rego @@ -8,6 +8,7 @@ CxPolicy[result] { not common_lib.valid_key(cluster, "private_cluster_enabled") + result := { "documentId": input.document[i].id, "resourceType": "azurerm_kubernetes_cluster", diff --git a/assets/queries/terraform/azure/aks_private_cluster_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/aks_private_cluster_disabled/test/positive_expected_result.json index 96f56ad6e79..a32127af4ce 100644 --- a/assets/queries/terraform/azure/aks_private_cluster_disabled/test/positive_expected_result.json +++ b/assets/queries/terraform/azure/aks_private_cluster_disabled/test/positive_expected_result.json @@ -3,12 +3,24 @@ "queryName": "AKS Private Cluster Disabled", "severity": "MEDIUM", "line": 7, - "fileName": "positive1.tf" + "filename": "positive1.tf", + "resourceType": "azurerm_kubernetes_cluster", + "resourceName": "example-aks1", + "searchKey": "azurerm_kubernetes_cluster[positive1].private_cluster_enabled", + "searchValue": "", + "expectedValue": "'azurerm_kubernetes_cluster[positive1].private_cluster_enabled' should be set to true", + "actualValue": "'azurerm_kubernetes_cluster[positive1].private_cluster_enabled' is set to false" }, { "queryName": "AKS Private Cluster Disabled", "severity": "MEDIUM", "line": 1, - "fileName": "positive2.tf" + "filename": "positive2.tf", + "resourceType": "azurerm_kubernetes_cluster", + "resourceName": "example-aks1", + "searchKey": "azurerm_kubernetes_cluster[positive2]", + "searchValue": "", + "expectedValue": "'azurerm_kubernetes_cluster[positive2].private_cluster_enabled' should be defined and set to true", + "actualValue": "'azurerm_kubernetes_cluster[positive2].private_cluster_enabled' is undefined" } ] \ No newline at end of file From a64ba82981c456f62912ca2e8248cb6dc503dd87 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 10 Mar 2026 16:46:27 +0000 Subject: [PATCH 11/30] added requests to requirements file --- .github/scripts/validate-search-line/requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/scripts/validate-search-line/requirements.txt b/.github/scripts/validate-search-line/requirements.txt index dd870f614de..f2293605cf1 100644 --- a/.github/scripts/validate-search-line/requirements.txt +++ b/.github/scripts/validate-search-line/requirements.txt @@ -1,2 +1 @@ -# No external dependencies required. -# Changed files are provided by dorny/paths-filter via CHANGED_QUERIES env var. +requests From cc2b2efa3631bf22a1ac43d66f41792003523d79 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Wed, 11 Mar 2026 10:12:46 +0000 Subject: [PATCH 12/30] changed searchLine to get -1 value --- .../azure/app_service_slot_managed_identity_disabled/query.rego | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/query.rego b/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/query.rego index 461d0689679..113cea9aa92 100644 --- a/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/query.rego +++ b/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/query.rego @@ -18,7 +18,7 @@ CxPolicy[result] { "issueType": "MissingAttribute", "keyExpectedValue": "'type' field should have the values 'SystemAssigned' or 'UserAssigned' defined inside the 'identity' block", "keyActualValue": "'identity' block is not defined", - "searchLine": common_lib.build_search_line(["resource", resource_types[idx_type], name], []), + "searchLine": common_lib.build_search_line(["testeTeste"], []), "remediationType": "addition", "remediation": "identity {\n\t\ttype = \"SystemAssigned, UserAssigned\"\n\t}", } From 2c9999328d2a447f2eb061adb47f689763ff0c94 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Wed, 11 Mar 2026 11:36:04 +0000 Subject: [PATCH 13/30] added debug prints --- .github/scripts/validate-search-line/validate-search-line.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/scripts/validate-search-line/validate-search-line.py b/.github/scripts/validate-search-line/validate-search-line.py index 7cb6f52864d..bc61f4a89bd 100644 --- a/.github/scripts/validate-search-line/validate-search-line.py +++ b/.github/scripts/validate-search-line/validate-search-line.py @@ -85,6 +85,9 @@ def validate_query_results(query_dir): print("No test files found") return True + for i, entry in range(test_files): + print(f"test_files[{i}]: {entry}\n") + # Run KICS on test directory using the compiled binary kics_binary = './bin/kics' cmd = [ @@ -153,6 +156,8 @@ def main(): # Find modified queries modified_queries = find_modified_queries(pr_files) + for i, entry in range(modified_queries): + print(f"modified_queries[{i}: {entry}]\n") if not modified_queries: print("No modified queries found in this PR") From e46f8ff608c35b7f0e4a932ac5e5a45038fc9392 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Wed, 11 Mar 2026 11:40:50 +0000 Subject: [PATCH 14/30] added exception type --- .github/scripts/validate-search-line/validate-search-line.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/scripts/validate-search-line/validate-search-line.py b/.github/scripts/validate-search-line/validate-search-line.py index bc61f4a89bd..9c1aceda4b1 100644 --- a/.github/scripts/validate-search-line/validate-search-line.py +++ b/.github/scripts/validate-search-line/validate-search-line.py @@ -56,7 +56,6 @@ def find_modified_queries(files): if file.startswith('assets/queries/') and file.endswith('/query.rego'): query_dir = str(Path(file).parent) modified_queries.append(query_dir) - return modified_queries def find_test_fixtures(query_dir): @@ -117,7 +116,7 @@ def validate_query_results(query_dir): try: with open('/tmp/kics-result.json', 'r') as f: results = json.load(f) - except: + except (FileNotFoundError, json.JSONDecodeError): print("No results file generated") return True From 8a6e90572ba2ad1cc2a81c6f7661db9159b74548 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Wed, 11 Mar 2026 11:50:07 +0000 Subject: [PATCH 15/30] fixing error in script --- .github/scripts/validate-search-line/validate-search-line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/validate-search-line/validate-search-line.py b/.github/scripts/validate-search-line/validate-search-line.py index 9c1aceda4b1..696f37b1476 100644 --- a/.github/scripts/validate-search-line/validate-search-line.py +++ b/.github/scripts/validate-search-line/validate-search-line.py @@ -155,7 +155,7 @@ def main(): # Find modified queries modified_queries = find_modified_queries(pr_files) - for i, entry in range(modified_queries): + for i, entry in enumerate(modified_queries): print(f"modified_queries[{i}: {entry}]\n") if not modified_queries: From b869010863f7f1969819a2ce746566128fb1366b Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Mon, 16 Mar 2026 10:20:35 +0000 Subject: [PATCH 16/30] removed trailing whitespace --- .github/scripts/validate-search-line/validate-search-line.py | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/scripts/validate-search-line/validate-search-line.py b/.github/scripts/validate-search-line/validate-search-line.py index 696f37b1476..5553a7f1963 100644 --- a/.github/scripts/validate-search-line/validate-search-line.py +++ b/.github/scripts/validate-search-line/validate-search-line.py @@ -63,7 +63,6 @@ def find_test_fixtures(query_dir): test_dir = Path(query_dir) / 'test' if not test_dir.exists(): return [] - fixtures = [] for item in test_dir.glob('positive*.json'): fixtures.append(str(item)) From 989adfe08c08748d11d59f782dcea7914b72a8d9 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Mon, 16 Mar 2026 10:53:13 +0000 Subject: [PATCH 17/30] changed to run the other script --- .github/workflows/go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index a0a95405f63..978472e9908 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -124,7 +124,7 @@ jobs: working-directory: .github/scripts/validate-search-line/ run: | pip3 install -r requirements.txt - python3 validate-search-line.py + python3 validate-search-line-lite.py security-scan: name: security-scan From cdb60a50b4df602978ff14ef70d9152f3f54bd82 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Mon, 16 Mar 2026 11:29:13 +0000 Subject: [PATCH 18/30] debugging test directory path --- .../scripts/validate-search-line/validate-search-line.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/scripts/validate-search-line/validate-search-line.py b/.github/scripts/validate-search-line/validate-search-line.py index 5553a7f1963..2b9f3483cd3 100644 --- a/.github/scripts/validate-search-line/validate-search-line.py +++ b/.github/scripts/validate-search-line/validate-search-line.py @@ -35,7 +35,8 @@ def fetch_pr_files(): while page < 50: response = fetch(page, max_items) if response['status'] != 200: - return exit_with_error(f'Failed to fetch PR files\n- status code: {response["status"]}') + print(f"::error::Failed to fetch PR files\n- status code: {response["status"]}") + return sys.exit(1) for obj in response['data']: if obj['status'] != 'removed': @@ -46,7 +47,8 @@ def fetch_pr_files(): page += 1 - return exit_with_error('Failed to fetch PR files - too many pages') + print("::error::Failed to fetch PR files - too many pages") + return sys.exit(1) def find_modified_queries(files): """Find modified query files (query.rego files)""" @@ -75,7 +77,7 @@ def validate_query_results(query_dir): test_dir = Path(query_dir) / 'test' if not test_dir.exists(): - print("No test directory found") + print(f"No test directory found with the path: {test_dir}") return True test_files = list(test_dir.glob('positive*')) + list(test_dir.glob('negative*')) From d37f8c5583095b705855746bf86fd7a59b745aef Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Mon, 16 Mar 2026 13:36:26 +0000 Subject: [PATCH 19/30] changed scripts that run on the action --- .../validate-search-line-lite.py | 172 ----------- .../validate-search-line.py | 267 ++++++++---------- .github/workflows/go-ci.yml | 2 +- 3 files changed, 119 insertions(+), 322 deletions(-) delete mode 100644 .github/scripts/validate-search-line/validate-search-line-lite.py diff --git a/.github/scripts/validate-search-line/validate-search-line-lite.py b/.github/scripts/validate-search-line/validate-search-line-lite.py deleted file mode 100644 index cf5c45a8991..00000000000 --- a/.github/scripts/validate-search-line/validate-search-line-lite.py +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env python3 -""" -Validador de searchLine para queries KICS (versao lite - analise estatica). -Recebe a lista de ficheiros alterados via CHANGED_QUERIES (JSON array do dorny/paths-filter). - -Validacoes: - 1. Se o query.rego tiver searchLine, nao pode estar hardcoded a -1 - 2. Nos expected results, se searchLine e line existirem: - - searchLine != -1 - - searchLine == line -""" - -import os -import re -import json -import sys -from pathlib import Path - -# O script corre a partir de .github/scripts/validate-search-line/ -REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent - - -def exit_with_error(message): - print(f"::error::{message}") - sys.exit(1) - - -def get_changed_queries(): - """Obtem a lista de query.rego alterados via env CHANGED_QUERIES (JSON array do dorny/paths-filter).""" - changed = os.getenv('CHANGED_QUERIES', '') - if not changed: - exit_with_error("CHANGED_QUERIES environment variable is empty or not set") - - try: - files = json.loads(changed) - except json.JSONDecodeError: - exit_with_error(f"CHANGED_QUERIES is not valid JSON: {changed}") - - # Extrair directorios unicos das queries - query_dirs = [] - for f in files: - if f.endswith('/query.rego'): - query_dirs.append(REPO_ROOT / Path(f).parent) - - return query_dirs - - -def validate_rego_file(rego_file): - """Valida que o query.rego define searchLine correctamente. - - - Se searchLine nao existir, skip (nao e obrigatorio) - - Se existir, nao pode estar hardcoded a -1 - """ - try: - content = rego_file.read_text() - except Exception as e: - print(f" ::warning file={rego_file}::Cannot read file: {e}") - return True - - if 'searchLine' not in content: - print(f" [SKIP] searchLine not found in {rego_file.name}") - return True - - # Detectar searchLine hardcoded a -1 - if re.search(r'"searchLine"\s*:\s*-1', content): - print(f" ::error file={rego_file.relative_to(REPO_ROOT)}::searchLine is hardcoded to -1") - return False - - # Verificar se usa build_search_line() (padrao recomendado) - if 'build_search_line' in content: - print(f" [OK] {rego_file.name} uses build_search_line()") - return True - - # Verificar se searchLine e atribuido a uma variavel - if re.search(r'"searchLine"\s*:\s*[a-zA-Z_]\w*', content): - print(f" [OK] {rego_file.name} has searchLine assigned to a variable") - return True - - print(f" ::warning file={rego_file.relative_to(REPO_ROOT)}::searchLine pattern unclear - review manually") - return True - - -def validate_expected_results(query_dir): - """Valida os ficheiros de expected results. - - Para cada resultado esperado que contenha searchLine e line: - - searchLine != -1 - - searchLine == line - """ - test_dir = query_dir / 'test' - - if not test_dir.exists(): - print(" [SKIP] No test directory found") - return True - - expected_files = list(test_dir.glob('*expected_result*.json')) - - if not expected_files: - print(" [SKIP] No expected result files found") - return True - - all_valid = True - for expected_file in expected_files: - try: - results = json.loads(expected_file.read_text()) - except json.JSONDecodeError: - print(" ::warning file={expected_file.relative_to(REPO_ROOT)}::Invalid JSON") - continue - except Exception as e: - print(f" ::warning file={expected_file.relative_to(REPO_ROOT)}::Error reading: {e}") - continue - - if not isinstance(results, list): - results = [results] - - for idx, result in enumerate(results): - search_line = result.get('searchLine') - line = result.get('line') - - if search_line is None or line is None: - continue - - rel_path = expected_file.relative_to(REPO_ROOT) - - if search_line == -1: - print(f" ::error file={rel_path}::Result [{idx}]: searchLine is -1 (line={line})") - all_valid = False - elif search_line != line: - print(f" ::error file={rel_path}::Result [{idx}]: searchLine ({search_line}) != line ({line})") - all_valid = False - else: - print(f" [OK] {rel_path}: result [{idx}] searchLine={search_line} matches line") - - return all_valid - - -def main(): - print("Starting searchLine validation (lite mode)...\n") - - # Obter queries alteradas do dorny/paths-filter - modified_queries = get_changed_queries() - - if not modified_queries: - print("No query.rego files found in CHANGED_QUERIES - nothing to validate") - sys.exit(0) - - print(f"Found {len(modified_queries)} modified queries to validate:\n") - - all_valid = True - for query_dir in modified_queries: - rel_dir = query_dir.relative_to(REPO_ROOT) - print(f"--- Validating: {rel_dir}") - - rego_file = query_dir / 'query.rego' - if rego_file.exists(): - if not validate_rego_file(rego_file): - all_valid = False - - if not validate_expected_results(query_dir): - all_valid = False - - print() - - if all_valid: - print("All searchLine validations passed!") - sys.exit(0) - else: - exit_with_error("Some searchLine validations failed. See errors above.") - - -if __name__ == "__main__": - main() diff --git a/.github/scripts/validate-search-line/validate-search-line.py b/.github/scripts/validate-search-line/validate-search-line.py index 2b9f3483cd3..d4fba4aabe9 100644 --- a/.github/scripts/validate-search-line/validate-search-line.py +++ b/.github/scripts/validate-search-line/validate-search-line.py @@ -1,180 +1,149 @@ #!/usr/bin/env python3 +""" +Validates searchLine in modified KICS queries. +Receives the list of changed files via CHANGED_QUERIES (JSON array from dorny/paths-filter). + +Validations (only run if query.rego defines searchLine): + 1. searchLine must not be hardcoded to -1 in query.rego + 2. In expected result files, searchLine must equal line +""" import os -import requests +import re import json -import subprocess import sys from pathlib import Path -KICS_PR_NUMBER = os.getenv('KICS_PR_NUMBER') -KICS_GITHUB_TOKEN = os.getenv('KICS_GITHUB_TOKEN') +# Script lives at .github/scripts/validate-search-line/ +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent + def exit_with_error(message): print(f"::error::{message}") sys.exit(1) -def exit_success(): - print("All searchLine validations passed!") - sys.exit(0) - -def fetch(page=1, max_items=100): - """Fetch PR files from GitHub API""" - print(f'Fetching PR #{KICS_PR_NUMBER} files... page {page}') - headers = {'Authorization': f'token {KICS_GITHUB_TOKEN}'} - url = f'https://api.github.com/repos/checkmarx/kics/pulls/{KICS_PR_NUMBER}/files?per_page={max_items}&page={page}' - response = requests.get(url, headers=headers) - return {"data": response.json(), "status": response.status_code} - -def fetch_pr_files(): - """Fetch all files modified in the PR""" - files = [] - page = 1 - max_items = 100 - - while page < 50: - response = fetch(page, max_items) - if response['status'] != 200: - print(f"::error::Failed to fetch PR files\n- status code: {response["status"]}") - return sys.exit(1) - - for obj in response['data']: - if obj['status'] != 'removed': - files.append(obj['filename']) - - if len(response['data']) < max_items: - return files - - page += 1 - - print("::error::Failed to fetch PR files - too many pages") - return sys.exit(1) - -def find_modified_queries(files): - """Find modified query files (query.rego files)""" - modified_queries = [] - for file in files: - # Match patterns like assets/queries/terraform/*/query.rego - if file.startswith('assets/queries/') and file.endswith('/query.rego'): - query_dir = str(Path(file).parent) - modified_queries.append(query_dir) - return modified_queries - -def find_test_fixtures(query_dir): - """Find test fixtures for a query""" - test_dir = Path(query_dir) / 'test' - if not test_dir.exists(): - return [] - fixtures = [] - for item in test_dir.glob('positive*.json'): - fixtures.append(str(item)) - - return fixtures - -def validate_query_results(query_dir): - """Run KICS on test files and validate searchLine""" - print(f"\n🔍 Validating query: {query_dir}") - - test_dir = Path(query_dir) / 'test' - if not test_dir.exists(): - print(f"No test directory found with the path: {test_dir}") - return True - - test_files = list(test_dir.glob('positive*')) + list(test_dir.glob('negative*')) - if not test_files: - print("No test files found") - return True - - for i, entry in range(test_files): - print(f"test_files[{i}]: {entry}\n") - - # Run KICS on test directory using the compiled binary - kics_binary = './bin/kics' - cmd = [ - kics_binary, 'scan', - '-p', str(test_dir), - '--queries', str(query_dir), - '-o', '/tmp/kics-result.json', - '--output-formats', 'json', - '--exclude-paths', 'test' - ] - + +def get_changed_queries(): + """Get changed query.rego directories from CHANGED_QUERIES env var (JSON array from dorny/paths-filter).""" + changed = os.getenv('CHANGED_QUERIES', '') + if not changed: + exit_with_error("CHANGED_QUERIES environment variable is empty or not set") + try: - result = subprocess.run(cmd, capture_output=True, text=True, cwd='../..') - if result.returncode not in [0, 20, 40, 50]: # KICS returns different codes for different scenarios - print(f"KICS scan returned code {result.returncode}") - if result.stderr: - print(f" stderr: {result.stderr[:200]}") - return True # Don't fail the validation if KICS itself fails - except FileNotFoundError: - print(f"KICS binary not found at {kics_binary}") + files = json.loads(changed) + except json.JSONDecodeError: + exit_with_error(f"CHANGED_QUERIES is not valid JSON: {changed}") + + query_dirs = [] + for f in files: + if f.endswith('/query.rego'): + query_dirs.append(REPO_ROOT / Path(f).parent) + + return query_dirs + + +def validate_query(query_dir): + """Validate searchLine for a single query directory.""" + rel_dir = query_dir.relative_to(REPO_ROOT) + print(f"--- Validating: {rel_dir}") + + rego_file = query_dir / 'query.rego' + if not rego_file.exists(): + print(f" [SKIP] query.rego not found") return True + + try: + content = rego_file.read_text() except Exception as e: - print(f"Failed to run KICS: {e}") + print(f" ::warning file={rego_file.relative_to(REPO_ROOT)}::Cannot read file: {e}") return True - - # Validate results - try: - with open('/tmp/kics-result.json', 'r') as f: - results = json.load(f) - except (FileNotFoundError, json.JSONDecodeError): - print("No results file generated") + + if 'searchLine' not in content: + print(f" [SKIP] searchLine not defined in query.rego") return True - - all_valid = True - results_list = results.get('results', []) - - if not results_list: - print("No issues found in test files") + + # searchLine is defined — check if hardcoded to -1 + if re.search(r'"searchLine"\s*:\s*-1', content): + print(f" ::error file={rego_file.relative_to(REPO_ROOT)}::searchLine is hardcoded to -1") + return False + + print(f" [OK] query.rego defines searchLine correctly") + + return validate_expected_results(query_dir) + + +def validate_expected_results(query_dir): + """Validate expected result files: searchLine must equal line.""" + test_dir = query_dir / 'test' + + if not test_dir.exists(): + print(" [SKIP] No test directory found") return True - - for result in results_list: - search_line = result.get('searchLine', -1) - line = result.get('line', -1) - - # Validate searchLine - if search_line == -1: - print(f"searchLine is -1 for {result.get('fileName')}:{line}") - all_valid = False - elif search_line != line: - print(f"searchLine ({search_line}) != line ({line}) in {result.get('fileName')}") - all_valid = False - else: - print(f"{result.get('fileName')}:{line} - searchLine correctly set to {search_line}") - + + expected_files = list(test_dir.glob('*expected_result*.json')) + + if not expected_files: + print(" [SKIP] No expected result files found") + return True + + all_valid = True + for expected_file in expected_files: + try: + results = json.loads(expected_file.read_text()) + except json.JSONDecodeError: + print(f" ::warning file={expected_file.relative_to(REPO_ROOT)}::Invalid JSON") + continue + except Exception as e: + print(f" ::warning file={expected_file.relative_to(REPO_ROOT)}::Error reading: {e}") + continue + + if not isinstance(results, list): + results = [results] + + for idx, result in enumerate(results): + search_line = result.get('searchLine') + line = result.get('line') + + if search_line is None or line is None: + continue + + rel_path = expected_file.relative_to(REPO_ROOT) + + if search_line == -1: + print(f" ::error file={rel_path}::Result [{idx}]: searchLine is -1 (line={line})") + all_valid = False + elif search_line != line: + print(f" ::error file={rel_path}::Result [{idx}]: searchLine ({search_line}) != line ({line})") + all_valid = False + else: + print(f" [OK] {rel_path}: result [{idx}] searchLine={search_line} matches line") + return all_valid + def main(): print("Starting searchLine validation...\n") - - if not KICS_PR_NUMBER or not KICS_GITHUB_TOKEN: - exit_with_error("Missing KICS_PR_NUMBER or KICS_GITHUB_TOKEN environment variables") - - # Fetch modified files - pr_files = fetch_pr_files() - print(f"Found {len(pr_files)} modified files in PR\n") - - # Find modified queries - modified_queries = find_modified_queries(pr_files) - for i, entry in enumerate(modified_queries): - print(f"modified_queries[{i}: {entry}]\n") - + + modified_queries = get_changed_queries() + if not modified_queries: - print("No modified queries found in this PR") - exit_success() - - print(f"Found {len(modified_queries)} modified queries\n") - - # Validate each query + print("No query.rego files found in CHANGED_QUERIES - nothing to validate") + sys.exit(0) + + print(f"Found {len(modified_queries)} modified queries to validate:\n") + all_valid = True for query_dir in modified_queries: - if not validate_query_results(query_dir): + if not validate_query(query_dir): all_valid = False - + print() + if all_valid: - exit_success() + print("All searchLine validations passed!") + sys.exit(0) else: - exit_with_error("Some searchLine validations failed. Please fix the issues above.") + exit_with_error("Some searchLine validations failed. See errors above.") + if __name__ == "__main__": main() diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 978472e9908..a0a95405f63 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -124,7 +124,7 @@ jobs: working-directory: .github/scripts/validate-search-line/ run: | pip3 install -r requirements.txt - python3 validate-search-line-lite.py + python3 validate-search-line.py security-scan: name: security-scan From 5ee189250ac90861630b91032f3ba43c9e5373f1 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Mon, 16 Mar 2026 13:44:22 +0000 Subject: [PATCH 20/30] removed f-string without placeholders --- .../scripts/validate-search-line/validate-search-line.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/scripts/validate-search-line/validate-search-line.py b/.github/scripts/validate-search-line/validate-search-line.py index d4fba4aabe9..66530b94d79 100644 --- a/.github/scripts/validate-search-line/validate-search-line.py +++ b/.github/scripts/validate-search-line/validate-search-line.py @@ -49,7 +49,7 @@ def validate_query(query_dir): rego_file = query_dir / 'query.rego' if not rego_file.exists(): - print(f" [SKIP] query.rego not found") + print(" [SKIP] query.rego not found") return True try: @@ -59,7 +59,7 @@ def validate_query(query_dir): return True if 'searchLine' not in content: - print(f" [SKIP] searchLine not defined in query.rego") + print(" [SKIP] searchLine not defined in query.rego") return True # searchLine is defined — check if hardcoded to -1 @@ -67,7 +67,7 @@ def validate_query(query_dir): print(f" ::error file={rego_file.relative_to(REPO_ROOT)}::searchLine is hardcoded to -1") return False - print(f" [OK] query.rego defines searchLine correctly") + print(" [OK] query.rego defines searchLine correctly") return validate_expected_results(query_dir) From ee9a137242951feeb553b9018efa5033ca556501 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Mon, 16 Mar 2026 15:37:21 +0000 Subject: [PATCH 21/30] inverted changes on the query --- .../aks_private_cluster_disabled/query.rego | 1 - .../test/positive_expected_result.json | 16 ++-------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/assets/queries/terraform/azure/aks_private_cluster_disabled/query.rego b/assets/queries/terraform/azure/aks_private_cluster_disabled/query.rego index eda41b0c2a3..7bc9c6c6b62 100644 --- a/assets/queries/terraform/azure/aks_private_cluster_disabled/query.rego +++ b/assets/queries/terraform/azure/aks_private_cluster_disabled/query.rego @@ -8,7 +8,6 @@ CxPolicy[result] { not common_lib.valid_key(cluster, "private_cluster_enabled") - result := { "documentId": input.document[i].id, "resourceType": "azurerm_kubernetes_cluster", diff --git a/assets/queries/terraform/azure/aks_private_cluster_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/aks_private_cluster_disabled/test/positive_expected_result.json index a32127af4ce..eaf17586270 100644 --- a/assets/queries/terraform/azure/aks_private_cluster_disabled/test/positive_expected_result.json +++ b/assets/queries/terraform/azure/aks_private_cluster_disabled/test/positive_expected_result.json @@ -3,24 +3,12 @@ "queryName": "AKS Private Cluster Disabled", "severity": "MEDIUM", "line": 7, - "filename": "positive1.tf", - "resourceType": "azurerm_kubernetes_cluster", - "resourceName": "example-aks1", - "searchKey": "azurerm_kubernetes_cluster[positive1].private_cluster_enabled", - "searchValue": "", - "expectedValue": "'azurerm_kubernetes_cluster[positive1].private_cluster_enabled' should be set to true", - "actualValue": "'azurerm_kubernetes_cluster[positive1].private_cluster_enabled' is set to false" + "filename": "positive1.tf" }, { "queryName": "AKS Private Cluster Disabled", "severity": "MEDIUM", "line": 1, - "filename": "positive2.tf", - "resourceType": "azurerm_kubernetes_cluster", - "resourceName": "example-aks1", - "searchKey": "azurerm_kubernetes_cluster[positive2]", - "searchValue": "", - "expectedValue": "'azurerm_kubernetes_cluster[positive2].private_cluster_enabled' should be defined and set to true", - "actualValue": "'azurerm_kubernetes_cluster[positive2].private_cluster_enabled' is undefined" + "filename": "positive2.tf" } ] \ No newline at end of file From 8ed826197575e6634ee93d359ccf5e143e4ad219 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Mon, 16 Mar 2026 15:59:02 +0000 Subject: [PATCH 22/30] changed positive_expected_result --- .../test/positive_expected_result.json | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/test/positive_expected_result.json index af3369be556..eb0bd11d7d7 100644 --- a/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/test/positive_expected_result.json +++ b/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/test/positive_expected_result.json @@ -3,18 +3,36 @@ "queryName": "Beta - App Service Slot Managed Identity Disabled", "severity": "MEDIUM", "line": 1, - "fileName": "positive1.tf" + "fileName": "positive1.tf", + "resourceType": "azurerm_app_service_slot", + "resourceName": "${random_id.server.hex}", + "searchKey": "azurerm_app_service_slot[positive1]", + "searchValue": "", + "expectedValue": "'type' field should have the values 'SystemAssigned' or 'UserAssigned' defined inside the 'identity' block", + "actualValue": "'identity' block is not defined" }, { "queryName": "Beta - App Service Slot Managed Identity Disabled", "severity": "MEDIUM", "line": 1, - "fileName": "positive2.tf" + "fileName": "positive2.tf", + "resourceType": "azurerm_linux_web_app_slot", + "resourceName": "example-slot", + "searchKey": "azurerm_linux_web_app_slot[positive2]", + "searchValue": "", + "expectedValue": "'type' field should have the values 'SystemAssigned' or 'UserAssigned' defined inside the 'identity' block", + "actualValue": "'identity' block is not defined" }, { "queryName": "Beta - App Service Slot Managed Identity Disabled", "severity": "MEDIUM", "line": 1, - "fileName": "positive3.tf" + "fileName": "positive3.tf", + "resourceType": "azurerm_windows_web_app_slot", + "resourceName": "example-slot", + "searchKey": "azurerm_windows_web_app_slot[positive3]", + "searchValue": "", + "expectedValue": "'type' field should have the values 'SystemAssigned' or 'UserAssigned' defined inside the 'identity' block", + "actualValue": "'identity' block is not defined" } ] \ No newline at end of file From 4e92c3687ee8f16d488d596e6e80c3952a165f72 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Mon, 16 Mar 2026 16:01:52 +0000 Subject: [PATCH 23/30] changed filename to fileName --- .../test/positive_expected_result.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/queries/terraform/azure/aks_private_cluster_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/aks_private_cluster_disabled/test/positive_expected_result.json index eaf17586270..96f56ad6e79 100644 --- a/assets/queries/terraform/azure/aks_private_cluster_disabled/test/positive_expected_result.json +++ b/assets/queries/terraform/azure/aks_private_cluster_disabled/test/positive_expected_result.json @@ -3,12 +3,12 @@ "queryName": "AKS Private Cluster Disabled", "severity": "MEDIUM", "line": 7, - "filename": "positive1.tf" + "fileName": "positive1.tf" }, { "queryName": "AKS Private Cluster Disabled", "severity": "MEDIUM", "line": 1, - "filename": "positive2.tf" + "fileName": "positive2.tf" } ] \ No newline at end of file From c1942b4f2818fba9a72a582a0f49343bf81a8015 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Mon, 16 Mar 2026 16:50:29 +0000 Subject: [PATCH 24/30] added print for debugging processes --- .github/scripts/validate-search-line/validate-search-line.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/scripts/validate-search-line/validate-search-line.py b/.github/scripts/validate-search-line/validate-search-line.py index 66530b94d79..93e08899275 100644 --- a/.github/scripts/validate-search-line/validate-search-line.py +++ b/.github/scripts/validate-search-line/validate-search-line.py @@ -63,9 +63,11 @@ def validate_query(query_dir): return True # searchLine is defined — check if hardcoded to -1 - if re.search(r'"searchLine"\s*:\s*-1', content): + if re.search(r'"searchLine"\s*:\s*-1\b', content): print(f" ::error file={rego_file.relative_to(REPO_ROOT)}::searchLine is hardcoded to -1") return False + else: + print(f"re.search returned false for the following content: {content}") print(" [OK] query.rego defines searchLine correctly") From f714b7e7c8fd7f14e01c0a7930159981f533ef45 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Mon, 16 Mar 2026 17:45:59 +0000 Subject: [PATCH 25/30] added print to see content value --- .github/scripts/validate-search-line/validate-search-line.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/scripts/validate-search-line/validate-search-line.py b/.github/scripts/validate-search-line/validate-search-line.py index 93e08899275..dd312aca416 100644 --- a/.github/scripts/validate-search-line/validate-search-line.py +++ b/.github/scripts/validate-search-line/validate-search-line.py @@ -63,11 +63,10 @@ def validate_query(query_dir): return True # searchLine is defined — check if hardcoded to -1 + print(f"I will check if re.search matches the regex with the following content: {content}") if re.search(r'"searchLine"\s*:\s*-1\b', content): print(f" ::error file={rego_file.relative_to(REPO_ROOT)}::searchLine is hardcoded to -1") return False - else: - print(f"re.search returned false for the following content: {content}") print(" [OK] query.rego defines searchLine correctly") From 6a74250cdb2679528aede9e66701b7fae1883bca Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 17 Mar 2026 11:46:20 +0000 Subject: [PATCH 26/30] changed script --- .../validate-search-line/requirements.txt | 2 +- .../validate-search-line.py | 235 +++++++++++------- 2 files changed, 148 insertions(+), 89 deletions(-) diff --git a/.github/scripts/validate-search-line/requirements.txt b/.github/scripts/validate-search-line/requirements.txt index f2293605cf1..8b137891791 100644 --- a/.github/scripts/validate-search-line/requirements.txt +++ b/.github/scripts/validate-search-line/requirements.txt @@ -1 +1 @@ -requests + diff --git a/.github/scripts/validate-search-line/validate-search-line.py b/.github/scripts/validate-search-line/validate-search-line.py index dd312aca416..a0c07a71409 100644 --- a/.github/scripts/validate-search-line/validate-search-line.py +++ b/.github/scripts/validate-search-line/validate-search-line.py @@ -1,141 +1,199 @@ #!/usr/bin/env python3 """ Validates searchLine in modified KICS queries. -Receives the list of changed files via CHANGED_QUERIES (JSON array from dorny/paths-filter). -Validations (only run if query.rego defines searchLine): - 1. searchLine must not be hardcoded to -1 in query.rego - 2. In expected result files, searchLine must equal line +For each changed query that defines searchLine in query.rego: +1. Runs a KICS scan against the query's test files +2. Validates that every result's search_line equals its line +3. Validates that no result has search_line == -1 + +Queries that don't define searchLine are skipped (PASS). """ -import os -import re import json +import os +import subprocess import sys from pathlib import Path -# Script lives at .github/scripts/validate-search-line/ REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent -def exit_with_error(message): - print(f"::error::{message}") - sys.exit(1) - - def get_changed_queries(): - """Get changed query.rego directories from CHANGED_QUERIES env var (JSON array from dorny/paths-filter).""" - changed = os.getenv('CHANGED_QUERIES', '') - if not changed: - exit_with_error("CHANGED_QUERIES environment variable is empty or not set") + """Parse CHANGED_QUERIES env var (JSON array from dorny/paths-filter) to get query directories.""" + raw = os.getenv("CHANGED_QUERIES", "") + if not raw: + print("::error::CHANGED_QUERIES environment variable is empty or not set") + sys.exit(1) try: - files = json.loads(changed) + files = json.loads(raw) except json.JSONDecodeError: - exit_with_error(f"CHANGED_QUERIES is not valid JSON: {changed}") + print(f"::error::CHANGED_QUERIES is not valid JSON: {raw}") + sys.exit(1) - query_dirs = [] + dirs = [] for f in files: - if f.endswith('/query.rego'): - query_dirs.append(REPO_ROOT / Path(f).parent) + if f.endswith("/query.rego"): + dirs.append(REPO_ROOT / Path(f).parent) + return dirs - return query_dirs +def has_search_line_defined(query_dir): + """Check if query.rego defines searchLine in its result object.""" + rego_file = query_dir / "query.rego" + if not rego_file.exists(): + return False + return "searchLine" in rego_file.read_text() -def validate_query(query_dir): - """Validate searchLine for a single query directory.""" - rel_dir = query_dir.relative_to(REPO_ROOT) - print(f"--- Validating: {rel_dir}") - rego_file = query_dir / 'query.rego' - if not rego_file.exists(): - print(" [SKIP] query.rego not found") - return True +def get_query_id(query_dir): + """Read query ID from metadata.json.""" + return json.loads((query_dir / "metadata.json").read_text())["id"] - try: - content = rego_file.read_text() - except Exception as e: - print(f" ::warning file={rego_file.relative_to(REPO_ROOT)}::Cannot read file: {e}") - return True - if 'searchLine' not in content: - print(" [SKIP] searchLine not defined in query.rego") - return True +def run_kics_scan(query_dir): + """Run KICS scan for a single query and return True if it completed successfully.""" + query_id = get_query_id(query_dir) - # searchLine is defined — check if hardcoded to -1 - print(f"I will check if re.search matches the regex with the following content: {content}") - if re.search(r'"searchLine"\s*:\s*-1\b', content): - print(f" ::error file={rego_file.relative_to(REPO_ROOT)}::searchLine is hardcoded to -1") - return False + results_dir = query_dir / "results" + results_dir.mkdir(exist_ok=True) - print(" [OK] query.rego defines searchLine correctly") + payloads_dir = query_dir / "payloads" + payloads_dir.mkdir(exist_ok=True) - return validate_expected_results(query_dir) + cmd = [ + "go", "run", str(REPO_ROOT / "cmd" / "console" / "main.go"), + "scan", + "-p", str(query_dir / "test"), + "-o", str(results_dir), + "--output-name", "all_results.json", + "-i", query_id, + "-d", str(payloads_dir / "all_payloads.json"), + "-v", + "--experimental-queries", + "--bom", + "--enable-openapi-refs", + "--ignore-on-exit", "results", + ] + print(f" Running scan with query ID: {query_id}") -def validate_expected_results(query_dir): - """Validate expected result files: searchLine must equal line.""" - test_dir = query_dir / 'test' + proc = subprocess.run(cmd, capture_output=True, text=True, cwd=str(REPO_ROOT)) - if not test_dir.exists(): - print(" [SKIP] No test directory found") - return True + if proc.returncode != 0: + print(f" ::error::Scan failed (exit code {proc.returncode})") + if proc.stdout: + print(f" stdout (last 500 chars): ...{proc.stdout[-500:]}") + if proc.stderr: + print(f" stderr (last 500 chars): ...{proc.stderr[-500:]}") + return False + + return True + + +def validate_scan_results(query_dir): + """ + Validate scan results: + - Sort results by: file_name, line, search_key, search_value, resource_type, + resource_name, query_name, expected_value, actual_value + - Fail if any search_line != line + - Fail if any search_line == -1 + """ + results_file = query_dir / "results" / "all_results.json" + rel_dir = query_dir.relative_to(REPO_ROOT) - expected_files = list(test_dir.glob('*expected_result*.json')) + if not results_file.exists(): + print(f" ::error file={rel_dir}::Results file not generated by scan") + return False - if not expected_files: - print(" [SKIP] No expected result files found") + data = json.loads(results_file.read_text()) + + # Flatten results from all queries, attaching query_name to each file entry + all_results = [] + for query in data.get("queries", []): + query_name = query.get("query_name", "") + for entry in query.get("files", []): + all_results.append({ + "file_name": entry.get("file_name", ""), + "line": entry.get("line", 0), + "search_key": entry.get("search_key", ""), + "search_value": entry.get("search_value", ""), + "resource_type": entry.get("resource_type", ""), + "resource_name": entry.get("resource_name", ""), + "query_name": query_name, + "expected_value": entry.get("expected_value", ""), + "actual_value": entry.get("actual_value", ""), + "search_line": entry.get("search_line", 0), + }) + + if not all_results: + print(" [OK] No results to validate") return True - all_valid = True - for expected_file in expected_files: - try: - results = json.loads(expected_file.read_text()) - except json.JSONDecodeError: - print(f" ::warning file={expected_file.relative_to(REPO_ROOT)}::Invalid JSON") - continue - except Exception as e: - print(f" ::warning file={expected_file.relative_to(REPO_ROOT)}::Error reading: {e}") - continue + # Sort by the specified fields + all_results.sort(key=lambda r: ( + r["file_name"], + r["line"], + r["search_key"], + r["search_value"], + r["resource_type"], + r["resource_name"], + r["query_name"], + r["expected_value"], + r["actual_value"], + )) + + # Validate each result + valid = True + for idx, r in enumerate(all_results): + sl = r["search_line"] + ln = r["line"] + fn = r["file_name"] + + if sl == -1: + print(f" ::error::Result [{idx}] {fn}: search_line is -1") + valid = False + elif sl != ln: + print(f" ::error::Result [{idx}] {fn}: search_line ({sl}) != line ({ln})") + valid = False + else: + print(f" [OK] Result [{idx}] {fn}: search_line={sl} == line={ln}") + + return valid - if not isinstance(results, list): - results = [results] - for idx, result in enumerate(results): - search_line = result.get('searchLine') - line = result.get('line') +def validate_query(query_dir): + """Validate a single query directory.""" + rel_dir = query_dir.relative_to(REPO_ROOT) + print(f"--- Validating: {rel_dir}") - if search_line is None or line is None: - continue + if not has_search_line_defined(query_dir): + print(" [SKIP] searchLine not defined in query.rego - PASS") + return True - rel_path = expected_file.relative_to(REPO_ROOT) + print(" searchLine is defined in query.rego - running scan...") - if search_line == -1: - print(f" ::error file={rel_path}::Result [{idx}]: searchLine is -1 (line={line})") - all_valid = False - elif search_line != line: - print(f" ::error file={rel_path}::Result [{idx}]: searchLine ({search_line}) != line ({line})") - all_valid = False - else: - print(f" [OK] {rel_path}: result [{idx}] searchLine={search_line} matches line") + if not run_kics_scan(query_dir): + return False - return all_valid + return validate_scan_results(query_dir) def main(): print("Starting searchLine validation...\n") - modified_queries = get_changed_queries() + query_dirs = get_changed_queries() - if not modified_queries: - print("No query.rego files found in CHANGED_QUERIES - nothing to validate") + if not query_dirs: + print("No query.rego files in CHANGED_QUERIES - nothing to validate") sys.exit(0) - print(f"Found {len(modified_queries)} modified queries to validate:\n") + print(f"Found {len(query_dirs)} modified queries to validate:\n") all_valid = True - for query_dir in modified_queries: - if not validate_query(query_dir): + for qd in query_dirs: + if not validate_query(qd): all_valid = False print() @@ -143,7 +201,8 @@ def main(): print("All searchLine validations passed!") sys.exit(0) else: - exit_with_error("Some searchLine validations failed. See errors above.") + print("::error::Some searchLine validations failed. See errors above.") + sys.exit(1) if __name__ == "__main__": From dcff885333b43c24d4b658e605de5b2324c3897c Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:07:58 +0000 Subject: [PATCH 27/30] testing searchLine != searchLine --- .../validate-search-line/validate-search-line.py | 16 ++-------------- .../query.rego | 2 +- .../test/positive_expected_result.json | 6 +++--- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/.github/scripts/validate-search-line/validate-search-line.py b/.github/scripts/validate-search-line/validate-search-line.py index a0c07a71409..19d39a705c6 100644 --- a/.github/scripts/validate-search-line/validate-search-line.py +++ b/.github/scripts/validate-search-line/validate-search-line.py @@ -47,14 +47,9 @@ def has_search_line_defined(query_dir): return "searchLine" in rego_file.read_text() -def get_query_id(query_dir): - """Read query ID from metadata.json.""" - return json.loads((query_dir / "metadata.json").read_text())["id"] - - def run_kics_scan(query_dir): """Run KICS scan for a single query and return True if it completed successfully.""" - query_id = get_query_id(query_dir) + query_id = json.loads((query_dir / "metadata.json").read_text())["id"] results_dir = query_dir / "results" results_dir.mkdir(exist_ok=True) @@ -165,8 +160,6 @@ def validate_scan_results(query_dir): def validate_query(query_dir): """Validate a single query directory.""" - rel_dir = query_dir.relative_to(REPO_ROOT) - print(f"--- Validating: {rel_dir}") if not has_search_line_defined(query_dir): print(" [SKIP] searchLine not defined in query.rego - PASS") @@ -181,21 +174,16 @@ def validate_query(query_dir): def main(): - print("Starting searchLine validation...\n") - query_dirs = get_changed_queries() if not query_dirs: - print("No query.rego files in CHANGED_QUERIES - nothing to validate") + print("No query.rego were changes - nothing to validate") sys.exit(0) - print(f"Found {len(query_dirs)} modified queries to validate:\n") - all_valid = True for qd in query_dirs: if not validate_query(qd): all_valid = False - print() if all_valid: print("All searchLine validations passed!") diff --git a/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/query.rego b/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/query.rego index 113cea9aa92..461d0689679 100644 --- a/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/query.rego +++ b/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/query.rego @@ -18,7 +18,7 @@ CxPolicy[result] { "issueType": "MissingAttribute", "keyExpectedValue": "'type' field should have the values 'SystemAssigned' or 'UserAssigned' defined inside the 'identity' block", "keyActualValue": "'identity' block is not defined", - "searchLine": common_lib.build_search_line(["testeTeste"], []), + "searchLine": common_lib.build_search_line(["resource", resource_types[idx_type], name], []), "remediationType": "addition", "remediation": "identity {\n\t\ttype = \"SystemAssigned, UserAssigned\"\n\t}", } diff --git a/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/test/positive_expected_result.json index eb0bd11d7d7..f8b02e1d75f 100644 --- a/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/test/positive_expected_result.json +++ b/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/test/positive_expected_result.json @@ -2,7 +2,7 @@ { "queryName": "Beta - App Service Slot Managed Identity Disabled", "severity": "MEDIUM", - "line": 1, + "line": 2, "fileName": "positive1.tf", "resourceType": "azurerm_app_service_slot", "resourceName": "${random_id.server.hex}", @@ -14,7 +14,7 @@ { "queryName": "Beta - App Service Slot Managed Identity Disabled", "severity": "MEDIUM", - "line": 1, + "line": 2, "fileName": "positive2.tf", "resourceType": "azurerm_linux_web_app_slot", "resourceName": "example-slot", @@ -26,7 +26,7 @@ { "queryName": "Beta - App Service Slot Managed Identity Disabled", "severity": "MEDIUM", - "line": 1, + "line": 2, "fileName": "positive3.tf", "resourceType": "azurerm_windows_web_app_slot", "resourceName": "example-slot", From 4d48c0ed3c4f836fef9b56a987575541b7196291 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 17 Mar 2026 16:12:24 +0000 Subject: [PATCH 28/30] removed unnecessary sorting on the results in execution context --- .../validate-search-line.py | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/.github/scripts/validate-search-line/validate-search-line.py b/.github/scripts/validate-search-line/validate-search-line.py index 19d39a705c6..5066266f2b4 100644 --- a/.github/scripts/validate-search-line/validate-search-line.py +++ b/.github/scripts/validate-search-line/validate-search-line.py @@ -104,21 +104,13 @@ def validate_scan_results(query_dir): data = json.loads(results_file.read_text()) - # Flatten results from all queries, attaching query_name to each file entry + # Flatten results from all queries all_results = [] for query in data.get("queries", []): - query_name = query.get("query_name", "") for entry in query.get("files", []): all_results.append({ "file_name": entry.get("file_name", ""), "line": entry.get("line", 0), - "search_key": entry.get("search_key", ""), - "search_value": entry.get("search_value", ""), - "resource_type": entry.get("resource_type", ""), - "resource_name": entry.get("resource_name", ""), - "query_name": query_name, - "expected_value": entry.get("expected_value", ""), - "actual_value": entry.get("actual_value", ""), "search_line": entry.get("search_line", 0), }) @@ -126,19 +118,6 @@ def validate_scan_results(query_dir): print(" [OK] No results to validate") return True - # Sort by the specified fields - all_results.sort(key=lambda r: ( - r["file_name"], - r["line"], - r["search_key"], - r["search_value"], - r["resource_type"], - r["resource_name"], - r["query_name"], - r["expected_value"], - r["actual_value"], - )) - # Validate each result valid = True for idx, r in enumerate(all_results): From b3d85ab80b96523496843b6f1f0c933d4f255416 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 17 Mar 2026 16:14:34 +0000 Subject: [PATCH 29/30] removed unused requirements.txt file --- .github/scripts/validate-search-line/requirements.txt | 1 - .../validate-search-line/validate-search-line.py | 10 ---------- .github/workflows/go-ci.yml | 4 +--- 3 files changed, 1 insertion(+), 14 deletions(-) delete mode 100644 .github/scripts/validate-search-line/requirements.txt diff --git a/.github/scripts/validate-search-line/requirements.txt b/.github/scripts/validate-search-line/requirements.txt deleted file mode 100644 index 8b137891791..00000000000 --- a/.github/scripts/validate-search-line/requirements.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/.github/scripts/validate-search-line/validate-search-line.py b/.github/scripts/validate-search-line/validate-search-line.py index 5066266f2b4..e804fed8406 100644 --- a/.github/scripts/validate-search-line/validate-search-line.py +++ b/.github/scripts/validate-search-line/validate-search-line.py @@ -1,14 +1,4 @@ #!/usr/bin/env python3 -""" -Validates searchLine in modified KICS queries. - -For each changed query that defines searchLine in query.rego: -1. Runs a KICS scan against the query's test files -2. Validates that every result's search_line equals its line -3. Validates that no result has search_line == -1 - -Queries that don't define searchLine are skipped (PASS). -""" import json import os diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index a0a95405f63..9bdf86e30c4 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -122,9 +122,7 @@ jobs: KICS_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} KICS_PR_NUMBER: ${{ github.event.number }} working-directory: .github/scripts/validate-search-line/ - run: | - pip3 install -r requirements.txt - python3 validate-search-line.py + run: python3 validate-search-line.py security-scan: name: security-scan From c374d8dd278d1719ad7c2027552363ac16b681a4 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 17 Mar 2026 16:37:40 +0000 Subject: [PATCH 30/30] reverter changes on positie_expected_results --- .../test/positive_expected_result.json | 30 ++++--------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/test/positive_expected_result.json index f8b02e1d75f..af3369be556 100644 --- a/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/test/positive_expected_result.json +++ b/assets/queries/terraform/azure/app_service_slot_managed_identity_disabled/test/positive_expected_result.json @@ -2,37 +2,19 @@ { "queryName": "Beta - App Service Slot Managed Identity Disabled", "severity": "MEDIUM", - "line": 2, - "fileName": "positive1.tf", - "resourceType": "azurerm_app_service_slot", - "resourceName": "${random_id.server.hex}", - "searchKey": "azurerm_app_service_slot[positive1]", - "searchValue": "", - "expectedValue": "'type' field should have the values 'SystemAssigned' or 'UserAssigned' defined inside the 'identity' block", - "actualValue": "'identity' block is not defined" + "line": 1, + "fileName": "positive1.tf" }, { "queryName": "Beta - App Service Slot Managed Identity Disabled", "severity": "MEDIUM", - "line": 2, - "fileName": "positive2.tf", - "resourceType": "azurerm_linux_web_app_slot", - "resourceName": "example-slot", - "searchKey": "azurerm_linux_web_app_slot[positive2]", - "searchValue": "", - "expectedValue": "'type' field should have the values 'SystemAssigned' or 'UserAssigned' defined inside the 'identity' block", - "actualValue": "'identity' block is not defined" + "line": 1, + "fileName": "positive2.tf" }, { "queryName": "Beta - App Service Slot Managed Identity Disabled", "severity": "MEDIUM", - "line": 2, - "fileName": "positive3.tf", - "resourceType": "azurerm_windows_web_app_slot", - "resourceName": "example-slot", - "searchKey": "azurerm_windows_web_app_slot[positive3]", - "searchValue": "", - "expectedValue": "'type' field should have the values 'SystemAssigned' or 'UserAssigned' defined inside the 'identity' block", - "actualValue": "'identity' block is not defined" + "line": 1, + "fileName": "positive3.tf" } ] \ No newline at end of file