Skip to content

feat(secrets): detect dotenv runtime dependencies for the Keyverse migration - #1199

Draft
seonghobae wants to merge 7 commits into
developfrom
feat/keyverse_dotenv_dependency_rules_20260909
Draft

feat(secrets): detect dotenv runtime dependencies for the Keyverse migration#1199
seonghobae wants to merge 7 commits into
developfrom
feat/keyverse_dotenv_dependency_rules_20260909

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Scope and authority

Implements source-detection work for ContextualWisdomLab/.github#2063. Keyverse remains the secret-lifecycle authority; its bootstrap repair stays in the Keyverse owner path. This PR adds declarative migration findings to AppGuardrail; it does not become another secret store or copy scanner logic into consumers.

Protected base: develop@e71d37e7c58118e6764c96ab7c4492fe33eed6f8.
Current exact head: 021a1f4c226e6d1bcb7575fe1d37a7140823611c.
Lifecycle: Draft / application-security checks GREEN except shared central CodeQL / independent post-last-push review required / not merge-authorized.

Changes

  • Six executable YAML pattern-regex rules: Python direct loaders/settings, Rust dotenv/dotenvy, Node dotenv, shell source/dot, and literal Compose/container dotenv transport.
  • Positive and negative controls through the owner parser/compiler, plus complete-scanner registration/execution tests.
  • Changelog fragment and doctoring with scope, limits, evidence and adoption boundary.
  • Fleet review added realistic direct-syntax coverage for Python typed assignments and Rust mutable let mut bindings.

TDD repairs

  • 0b23610142a93391774e41a5a9b34381b562896e added the Python typed-assignment contract before its production fix. The prior regex did not match values: dict[str, str] = dotenv_values(".env"); this is source-level deterministic RED, not a claimed hosted RED.
  • 0238485a0a9612aa395d999684eb12593b6a7d minimally extended only the Python loader assignment prefix with a bounded, same-line type-annotation allowance ([^=\r\n]{1,128}).
  • ca50e0fa8ebaa5a458bdb020c23e1734135e43fd exercised the typed assignment through the packaged _scan_file path as well as the parser/compiler contract.
  • 753b5c9e54a6267381ce3b7ce785394542f9feeb added a Rust positive control for let mut loaded = dotenvy::dotenv(); before changing the rule. The preceding regex accepted let name = ... but not the common mutable binding; direct regex evaluation is a deterministic source-level RED. No hosted RED is claimed because the production descendant followed before that generation settled.
  • 021a1f4c226e6d1bcb7575fe1d37a7140823611c minimally allows optional mut after let while preserving the same direct dotenv/dotenvy call boundary and the existing bounded-pattern controls.

These remain WARNING-level migration findings, not assertions of an exploitable secret leak. The central CWL gate must explicitly adopt released rule IDs for new production regressions while preserving an owner backlog for existing dependencies. No blanket warning-to-error change, source rewrite, or production cutover is included.

Current exact-head evidence

At exact head 021a1f4c226e6d1bcb7575fe1d37a7140823611c, Tests 34393095056, Security Scan 34393095065, SAST Semgrep 34393095096, Pinned HTTPS Coverage 34393095120, OpenSSF Evidence Coverage 34393095097, Retention Audit Coverage 34393095107, Scan path context coverage 34393095046, and Security Process 34393095051 are terminal SUCCESS. CodeQL PR 34393095073 is terminal FAILURE on the shared central CodeQL path. No qualifying review exists after the last production push.

The application/security GREEN above is exact-head evidence; it does not waive the shared CodeQL gate. Draft remains intentional until central CodeQL reaches terminal GREEN for this generation and a qualifying independent post-last-push review is present.

Limits

Direct bounded syntax only: no general AST/alias/taint analysis, dynamic wrappers/filenames, arbitrary Python type expressions beyond the bounded assignment prefix, Rust destructuring/pattern bindings beyond the declared simple let [mut] name = form, all complex YAML forms, implicit framework/Compose loading, or universal language support. No-finding is not proof of migration completion. The scanner does not follow the matched dotenv reference or read its values. Organization migration evidence should project rule ID/repository/revision/path/line/disposition rather than copy arbitrary matched source context.

No workflow duplication, provider credential expansion, force-push/destructive rebase, self-approval, synthetic status, no-op retrigger, scanner suppression, or gate weakening.

…e migration

Add six declarative pattern-regex rules in the canonical scanner owner. Preserve advisory interpretation and negative controls; do not equate dotenv syntax with a proven credential leak or completed migration.

Observed RED: 43 cases fail with rules absent. Local parser/compiler excerpt acceptance: 43 cases pass. Six complete-scanner integration cases are added but not locally executed; full CI, coverage, packaging, security and independent review remain required. No runtime scanner or vault implementation is copied into consumers.
@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact head 64305b18df06ed37f87d3c77ce8cf176567b1e43: six supported YAML regex rules, declared lexical limits, negative controls, regex complexity, package discovery and the six complete-scanner integration tests. The 43 local cases used the retrieved parser/compiler excerpt; full scanner tests and release gates are not claimed. This PR remains Draft and does not yet enable organization enforcement.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

expected='64305b18df06ed37f87d3c77ce8cf176567b1e43'
actual="$(git rev-parse HEAD)"
printf 'HEAD=%s\nEXPECTED=%s\n' "$actual" "$expected"
git diff --name-status "${expected}^" "$expected"
printf '\n--- STAT ---\n'
git diff --stat "${expected}^" "$expected"

printf '\n--- YAML FILES CHANGED: OUTLINES ---\n'
while IFS= read -r file; do
  case "$file" in
    *.yml|*.yaml) ast-grep outline "$file" --items all || true ;;
  esac
done < <(git diff --name-only "${expected}^" "$expected")

printf '\n--- CHANGED FILE CONTENTS ---\n'
while IFS= read -r file; do
  printf '\n===== %s =====\n' "$file"
  sed -n '1,360p' "$file"
done < <(git diff --name-only "${expected}^" "$expected")

printf '\n--- RELEVANT DEFINITIONS AND REFERENCES ---\n'
rg -n -i -C 3 \
  'pattern-regex|dotenv|dotenvy|env_file|package\.json|package discovery|complete.scanner|integration' \
  . \
  -g '!**/.git/**' -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' \
  | head -n 1800

Length of output: 2148


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- CHANGED FILE CONTENTS ---'
for file in \
  CHANGELOG.d/keyverse-dotenv-dependency-rules.md \
  docs/doctoring/dotenv_dependency_detection.md \
  scanner/rules/dotenv_dependencies.yml \
  tests/test_dotenv_dependency_rules.py \
  tests/test_dotenv_scanner_integration.py
do
  printf '\n===== %s =====\n' "$file"
  cat -n "$file"
done

printf '%s\n' '--- CANDIDATE SCANNER/RULE ENGINE FILES ---'
fd -t f . scanner tests | sort | sed -n '1,700p'

printf '%s\n' '--- RULE/SCANNER DEFINITIONS AND TEST EXECUTION REFERENCES ---'
rg -n -i -C 4 \
  'def (load|compile|scan|discover|parse)|class .*?(Rule|Scanner)|pattern_regex|pattern-regex|rules_dir|scanner/rules|yaml\.safe_load|dotenv_dependencies' \
  scanner tests \
  -g '*.py' -g '*.yml' -g '*.yaml' \
  | head -n 1600

Length of output: 68036


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- APPGUARDRAIL ENGINE: RULE PARSING, COMPILATION, AND DISCOVERY ---'
sed -n '880,1090p' scanner/cli/appguardrail.py

printf '%s\n' '--- APPGUARDRAIL ENGINE: FILE COLLECTION, LANGUAGE MAPPING, AND SCANNING ---'
rg -n -C 8 \
  'def _scan_file\b|def _collect_files\b|def _language|LANGUAGE|EXTENSION|suffix|SCAN_RULES|pattern\.search|pattern\.finditer' \
  scanner/cli/appguardrail.py

printf '%s\n' '--- PACKAGING METADATA CANDIDATES ---'
fd -t f -d 3 '^(pyproject\.toml|setup\.cfg|setup\.py|MANIFEST\.in|tox\.ini)$' . | sort

printf '%s\n' '--- PACKAGING RULE-INCLUSION REFERENCES ---'
rg -n -i -C 5 \
  'package.data|include.package.data|scanner\.rules|\.yml|\.yaml|setuptools|wheel|sdist' \
  pyproject.toml setup.cfg setup.py MANIFEST.in 2>/dev/null || true

printf '%s\n' '--- RULE LOAD CONTRACT TESTS ---'
rg -n -C 6 \
  '_load_packaged_regex_rules|SCAN_RULES|resources\.files|scanner\.rules' \
  tests -g '*.py' | head -n 1000

Length of output: 51993


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- COMPLETE _scan_file IMPLEMENTATION ---'
sed -n '2919,3055p' scanner/cli/appguardrail.py

printf '%s\n' '--- SHARED LANGUAGE EXTENSION DEFINITIONS ---'
fd -t f '^language\.py$' . | while IFS= read -r file; do
  printf '\n===== %s =====\n' "$file"
  cat -n "$file"
done

printf '%s\n' '--- FINDING CONTEXT FOR DOCUMENTATION AND EXAMPLES ---'
sed -n '2260,2335p' scanner/cli/appguardrail.py
rg -n -C 5 'NON_BLOCKING_CONTEXTS|def _finding_context\b|scanner-fixture|example|doc' \
  scanner/cli/appguardrail.py appguardrail_core \
  -g '*.py' | head -n 900

Length of output: 41183


@seonghobae I found two rule-scope defects.

  1. High — Rust rule scans every file.
    scanner/rules/dotenv_dependencies.yml, Line 28 declares languages: [rust]. However, appguardrail_core/language.py has no rust entry in LANGUAGE_EXTENSIONS. The rule compiler then assigns extensions=None, and _get_applicable_rules() applies it to every non-skipped extension. A Rust-shaped example in Python, YAML, Markdown outside docs/, or generated text can produce a rust-dotenv-runtime-load finding. Add the .rs language mapping or define the Rust extension for this rule. Add an integration negative control that scans a non-Rust file containing dotenvy::dotenv().

  2. Medium — env_file is not sufficient evidence of Compose transport.
    scanner/rules/dotenv_dependencies.yml, Line 49 applies compose-dotenv-transport to all file types because it uses languages: [generic]. Its first branch reports any env_file: .env value, even when the YAML is not a Compose descriptor. The result is a container-dependency finding for unrelated configuration. Limit the env_file branch to Compose-specific file paths or add bounded Compose document context. Add a negative control for a non-Compose YAML file with env_file: .env.

The package metadata includes the new YAML file. The six integration tests validate rule registration and _scan_file execution. They do not validate an installed distribution or the CLI collection path.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor Author

Hosted verification update — unchanged head 64305b1

Tests run 34367822591 passed on Python 3.11 and 3.13. I read job 102521009016: the complete python -m pytest -q execution reports 1050 passed, including the 43 new syntax/contract cases and six real SCAN_RULES/_scan_file integration cases. Tested merge revision: c58b060e7fa8e5a67d6bbfa1717d85efc196a11e (head above + base e71d37e7c58118e6764c96ab7c4492fe33eed6f8). This supersedes the earlier uncertainty about hosted scanner registration/execution; it does not change the accurate statement that the full engine was unavailable locally.

SAST Semgrep, Security Scan, Security Process and existing coverage workflows also report success. CodeQL run 34367822558 remains queued at this observation. Formal reviews currently return an empty list; the requested review has not become an independent approval. No merge, immutable release or central enforcement is claimed. The selected existing modules' 100% coverage is not whole-repository coverage.

Please use these exact coordinates when adopting this detector under ContextualWisdomLab/.github#2063. Do not publish matched source snippets or secret values into the migration inventory.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact head ca50e0fa8ebaa5a458bdb020c23e1734135e43fd after the fleet TDD repair. Focus on the bounded Python typed-assignment extension for dotenv_values(...), preservation of comment/import/string-literal negatives, regex complexity/boundedness, and the complete _scan_file integration case. Treat predecessor reviews as stale; this request is for the full current base→head delta.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

base='e71d37e7c58118e6764c96ab7c4492fe33eed6f8'
head='ca50e0fa8ebaa5a458bdb020c23e1734135e43fd'

printf '%s\n' '--- REVISION CHECK ---'
printf 'HEAD=%s\n' "$(git rev-parse HEAD)"
printf 'REQUESTED=%s\nBASE=%s\n' "$head" "$base"
git merge-base --is-ancestor "$base" "$head"
printf 'base_is_ancestor=yes\n'

printf '%s\n' '--- COMPLETE BASE-TO-HEAD CHANGESET ---'
git diff --name-status "$base" "$head"
git diff --stat "$base" "$head"
git diff --find-renames --unified=80 "$base" "$head" -- \
  scanner/rules/dotenv_dependencies.yml \
  tests/test_dotenv_dependency_rules.py \
  tests/test_dotenv_scanner_integration.py \
  docs/doctoring/dotenv_dependency_detection.md \
  CHANGELOG.d/keyverse-dotenv-dependency-rules.md

printf '%s\n' '--- RULE AND TEST STRUCTURE ---'
for file in scanner/rules/dotenv_dependencies.yml tests/test_dotenv_dependency_rules.py tests/test_dotenv_scanner_integration.py; do
  if test -f "$file"; then
    printf '\n===== OUTLINE: %s =====\n' "$file"
    ast-grep outline "$file" --items all || true
  fi
done

printf '%s\n' '--- FULL RULES AND TARGETED TESTS ---'
for file in scanner/rules/dotenv_dependencies.yml tests/test_dotenv_dependency_rules.py tests/test_dotenv_scanner_integration.py; do
  if test -f "$file"; then
    printf '\n===== %s =====\n' "$file"
    cat -n "$file"
  fi
done

printf '%s\n' '--- RULE LOADING, APPLICABILITY, AND SCAN PATH ---'
rg -n -C 10 \
  'def _load_packaged_regex_rules\b|def _get_applicable_rules\b|def _scan_file\b|LANGUAGE_EXTENSIONS|pattern_regex|pattern-regex|extensions' \
  scanner appguardrail_core tests \
  -g '*.py' -g '*.yml' -g '*.yaml' \
  | head -n 2400

Length of output: 175119


@seonghobae Review complete for exact head ca50e0fa8ebaa5a458bdb020c23e1734135e43fd.

The typed Python assignment extension is bounded to one line and 128 characters. It preserves the tested comment, import, and string-literal negatives. The _scan_file integration test exercises the typed assignment. I found these remaining scope defects:

  1. High — The Rust rule applies to every scanned extension.
    scanner/rules/dotenv_dependencies.yml Line 28 declares languages: [rust], but appguardrail_core/language.py has no rust entry. _extensions_for_languages() therefore returns None, and _get_applicable_rules() applies the rule to all files. A Python, YAML, Markdown, or other non-Rust file that starts with dotenvy::dotenv() can produce a Rust finding. Add a Rust extension mapping for .rs, or give this rule an explicit extension. Add an _scan_file negative integration case for a non-Rust file containing the Rust-shaped syntax.

  2. Medium — The env_file branch reports unrelated YAML.
    scanner/rules/dotenv_dependencies.yml Line 49 marks the entire compose-dotenv-transport rule as generic. The env_file: alternative has no Compose path or document constraint. Any YAML configuration with env_file: .env produces a container transport finding, even when it is not a Compose descriptor. Scope this branch to supported Compose filenames or add bounded Compose-specific document context. Add a negative integration case for a non-Compose YAML file with env_file: .env.

You are interacting with an AI system.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CWL migration checkpoint: current exact head 021a1f4c226e6d1bcb7575fe1d37a7140823611c has Tests, Security Scan, SAST Semgrep, OpenSSF Evidence, Retention Audit, Pinned HTTPS, Scan-path and Security Process GREEN. CodeQL PR is terminal failure only because the compatibility job is waiting for the central dispatched exact-head CodeQL verdict; its log does not identify a source vulnerability. Do not weaken or bypass that gate. The rule PR remains Draft until the central verdict and independent review are terminal.

@seonghobae seonghobae added enhancement New feature or request priority: medium Normal-priority or P2 work labels Sep 12, 2026 — with ChatGPT Codex Connector
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request priority: medium Normal-priority or P2 work

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

1 participant