fix(intelligence): classify env files by evidence not filename (#122)#127
Conversation
Environment files were flagged as a critical secret exposure whenever a
basename started with ".env", so templates such as .env.example,
.env.sample, .env.template, and .env.dist were reported as exposed
secrets and carried rotation advice with no supporting evidence.
Classify each committed environment file by content into one of three
evidence classes -- template present, runtime env file present, or
secret-like value detected -- and size the review finding accordingly.
Only a non-placeholder secret-like value raises a critical finding with
rotation advice. Empty values, explicit placeholders, ${VAR} references,
and example credentials embedded in URLs (e.g. user:password@host or
user:${PW}@host) no longer trigger a critical incident. Findings name
their evidence class and sensitive key names only, never values.
Cached intelligence built before this change is refreshed so stale
records cannot surface a filename-only critical finding.
parthrohit22
left a comment
There was a problem hiding this comment.
Thanks for the focused fix and detailed test coverage. I revalidated the exact PR head locally: the focused tests pass, the full backend suite reports 460 passed / 3 skipped, and CI and CodeQL are green.
Before approval, please address these blocking cases:
-
The key-name heuristic still produces critical false positives for ordinary configuration values. For example,
PASSWORD_MIN_LENGTH=12,TOKEN_EXPIRY_SECONDS=3600,CLIENT_SECRET_REQUIRED=true,API_KEY_ENABLED=false, andPRIVATE_KEY_PATH=/run/keys/service.pemare all classified as exposed secrets and receive rotation advice. Critical severity needs credible value evidence, not only a security-related key-name match. -
Dotenv placeholders followed by inline comments are misclassified. Examples include
TOKEN=${TOKEN} # access tokenandAPI_KEY="${API_KEY}" # required. Please parse quoting and inline comments before placeholder evaluation and add regression coverage. -
from_record()fully rebuilds cached intelligence whenever an older record has environment files but no new evidence field. Because the rebuilt result is not persisted, every architecture, dependency, review, documentation, analysis, or AI request can rebuild the repository again. Please persist/migrate the compatibility upgrade or use a bounded fallback that does not invalidate the shared cache on every read.
Please add regression tests for these cases, rerun npm run test:backend, and update the acceptance-criteria/checklist claims accordingly. The issue-claim checkbox is also currently unchecked, and Issue #122 contains no claim comment, although it is assigned.
Requesting changes until these points are resolved.
parthrohit22
left a comment
There was a problem hiding this comment.
Reviewed against #122 — all six acceptance criteria are implemented and tested, and CI is green.
Particularly good: content wins over filename in both directions (a .env.example with a real secret is still critical), the two-factor rule (sensitive key name + credible value shape) with regression tests for PASSWORD_MIN_LENGTH / CLIENT_SECRET_REQUIRED / PRIVATE_KEY_PATH, the no-value-leakage assertion, and the legacy-cache upgrade guarded by a test that fails if the repo is rebuilt.
One set of small fixes before merge — we're not spinning follow-up issues for changes this small, so please include them here:
-
SECRET_KEY_NAME_PATTERNgaps — a few very common credential key names don't match because of the end-anchor:AWS_ACCESS_KEY_ID(ends in_id) — suggestaccess_?key(?:_?id)?- plural
CREDENTIALS(e.g.SERVICE_CREDENTIALS) — suggestcredentials? PASSWD/PWDvariants (e.g.DB_PASSWD) — suggest addingpasswdandpwd
Something like:
r"(?:^|_)(?:api_?key|access_?key(?:_?id)?|auth_?token|client_?secret|credentials?|passw(?:or)?d|pwd|private_?key|secret_?key|secret|token)$"
parthrohit22
left a comment
There was a problem hiding this comment.
Re-reviewed after 5863a31 — the pattern update is exactly right and I independently ran the test files (47/47 pass) and probed the new key families by hand. But my review asked for three things and only one is complete:
-
✅ Pattern additions — done.
-
⚠️ Tests — the positive cases are in, but both negative guards I asked for are missing:GOOGLE_APPLICATION_CREDENTIALS=/run/secrets/sa.json→ must assert not flagged. This is now the most important test in the set:credentials?matches this ubiquitous key, and only the path-value gate stands between it and a critical false positive. I verified by hand that it behaves correctly today — that behavior needs a test pinning it or the next pattern edit regresses #122 silently.- A
CLIENT_ID-style key with a credible-shaped value → must assert not flagged.
Add both to
test_security_related_configuration_is_not_credible_secret_evidence(or a sibling). -
❌ Truncation indicator — unchanged. The critical finding still joins
secret_keys[:10]with no signal when keys were dropped. Append something like" and N more"whenlen(secret_keys) > 10, with a small test.
These were explicitly requested last round; please address all of them rather than a subset. Once the two guard tests and the truncation fix are in, this is approved — the detection logic itself is verified solid.
parthrohit22
left a comment
There was a problem hiding this comment.
Re-reviewed fc3cd00 — all three outstanding items are now complete, and I verified independently: ran both test files in an isolated worktree at this head (50/50 passing) and confirmed the commit touches only the two expected files.
- The
GOOGLE_APPLICATION_CREDENTIALSguard test pins exactly the interaction that mattered, andCLIENT_ID=client-Prod-12345678was a well-chosen value — credible-shaped, so it proves the key-name gate does the rejecting rather than the value gate. - Truncation now reports "and N more" only when keys were omitted, with the ≤10 text unchanged, and the test confirms the full key list stays intact in the evidence.
Approving. This closes out #122 properly — evidence-first classification, no value leakage, and every behavior pinned by a test. Nice work across the three rounds.
Summary
Environment-file review stopped treating a
.env-prefixed filename as proof of an exposed secret. The engineering review now classifies each committed environment file by its content and only raises a critical, rotation-worthy finding when a credible secret-like value is actually present.This is security-sensitive logic (it decides when the product declares a critical secret exposure) and is covered by explicit regression tests. Reviewer attention on the classification and placeholder-detection rules is requested.
Linked issue
Fixes #122
What changed
Backend —
app/intelligenceEnvironmentFileEvidencemodel with anevidence_classoftemplate_present,runtime_env_file_present, orsecret_like_value_detected, plus the sensitive key names (never values) that triggered detection.RepositoryIntelligenceEngine._discoverynow derives this evidence by reading each environment file's content. Classification is: content with a non-placeholder secret-like value →secret_like_value_detected; otherwise a.env.example/.env.sample/.env.template/.env.distname →template_present; otherwise →runtime_env_file_present.${VAR}/$VARreferences,<...>/[...]markers, and common example tokens (example,password,changeme,your-*, …) as non-secrets.user:password@host) now inspects the password and ignores it when it is itself a placeholder or a${VAR}reference — so a sampleDATABASE_URLin a template is not a critical finding.AWS_ACCESS_KEY_ID,SERVICE_CREDENTIALS,DB_PASSWD, andDB_PWD; these names still require a credible secret-like value before becoming critical evidence.GOOGLE_APPLICATION_CREDENTIALSremains configuration evidence, andCLIENT_ID-style keys remain non-critical even when their values look credential-shaped.from_recordrefreshes cached intelligence that predates this evidence, so a stale record cannot resurface a filename-only critical finding.Backend —
app/reviewenv-file-present(alwayscritical) finding is replaced by up to three evidence-sized findings:env-secret-like-value-detected(critical, security),env-runtime-file-present(medium, security), andenv-template-present(low, configuration). Each states its evidence class; only the secret-like finding advises rotation. Cached records without evidence fall back toruntime_env_file_present(never critical).and N morewhen additional names are omitted, so the report never truncates evidence silently.Docs
docs/architecture/REPOSITORY_INTELLIGENCE.mddocuments the evidence classes and the "filename is never proof, values are never surfaced, rotation advice only on secret-like evidence" behavior.Integration with latest
devdevafter feat(intelligence): add versioned snapshot query API #126 landed.app/review/review_service.pyanddocs/architecture/REPOSITORY_INTELLIGENCE.mdby retaining both fix(intelligence): classify env files by evidence not filename (#122) #127's environment-file evidence handling and feat(intelligence): add versioned snapshot query API #126's oversized-source-file evidence behavior.Root cause and behavior change
Root cause: discovery and the review builder flagged any file whose basename started with
.envas a critical secret exposure (Path(path).name.startswith(".env")), with no content analysis. A filename is evidence that environment configuration exists; it is not evidence that a secret was exposed.Behavior change:
.env.exampleand siblings are no longer critical by name. Placeholders, empty values,${VAR}references, and example credentials produce at most a low/medium informational finding. A real, non-placeholder secret-like value still produces acriticalfinding — but it names the evidence class and the sensitive key names only, never the value, and rotation advice appears only in that case.Acceptance criteria completed
template_present,low).${VAR}/$VAR,<...>, example tokens, and placeholder passwords inside URLs).secret_like_value_detected,critical), including common access-key ID, plural credential,PASSWD, andPWDvariants, while path-valuedGOOGLE_APPLICATION_CREDENTIALSandCLIENT_ID-style keys remain guarded against false positives.problem).secret_like_value_detected).tests/test_environment_file_review.py, including common credential variants, both negative guards, and the truncation indicator, plus updatedtests/test_review_evidence.py).Testing performed
Run from
apps/backendwith the project virtualenv (Python 3.13).python -m pytestis whatnpm run test:backendwraps.The 4 full-suite skips are the PostgreSQL/Redis-gated tests that skip without those services locally (per CONTRIBUTING §10); CI provides them.
Screenshots
Not applicable — no UI change.
Security and data considerations
EnvironmentFileEvidence, logs, tests, and docs carry only evidence classes, file paths, and sensitive key names. Test fixtures use obviously synthetic values, and a regression test asserts the value never appears in the finding._read_text(skips files > 512 KB, ignores unreadable/binary content).Dependencies and blocked work
None.
Scope changes or remaining work
None. The secret-like-value detector is intentionally conservative (key-name heuristics plus embedded-URL credentials); broadening it (e.g. entropy analysis) is out of scope for this false-positive fix.
Contributor checklist
devdevdev(merged after feat(intelligence): add versioned snapshot query API #126)Fixes) is used only because the issue is fully resolved