Skip to content

fix(pe3): treat ignore-file entries as path exclusions, not credential access - #787

Merged
rng1995 merged 2 commits into
mainfrom
naren/fix-pe3-ignore-file-entries
Oct 7, 2026
Merged

rng1995 merged 2 commits into
mainfrom
naren/fix-pe3-ignore-file-entries

Conversation

@rng1995

@rng1995 rng1995 commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

PE3 (Credential Access) matches credential file names lexically in every file type. A skill's .gitignore that lists .env under a # Environment & secrets comment is therefore reported as HIGH Credential Access, even though that entry keeps the file out of version control and reads nothing. The HIGH blocks downstream signing gates on an otherwise clean skill. It reproduces on 2.11.2, 2.12.0 and main (3a1ceee).

This PR treats a single path pattern on its own line of a gitignore-syntax exclusion file as a path exclusion, not credential access.

Customer impact

Reported internally: an internal skill's release gate fails on PE3 HIGH .gitignore:2 '.env' on every SkillSpector version. With this PR that single finding disappears. Nothing else in the report changes (30 → 29 issues; all other sections identical apart from scanned_at).

Root cause

  • static_patterns_privilege_escalation.py matches the bare .env pattern anywhere.
  • Ignore files have no file type of their own, so they infer as other.
  • The only existing .env qualifier (_is_env_file_reference_in_docs) applies to Markdown and text.

So nothing qualified the match.

Fix

A PE3 match is skipped, in the same way as the existing keyring-noun skip, only when all of these hold:

  1. The file is a gitignore-syntax exclusion list. That is .gitignore, .dockerignore (and <Dockerfile>.dockerignore), .npmignore, .helmignore, .gcloudignore or .vercelignore; the ESLint, Prettier or Stylelint ignore file; .ignore, .rgignore or .fdignore; or the Cursor, Gemini or Aider agent ignore files.
  2. The whole physical line is one pattern token containing the match. Lines are split on \n as those parsers do, with a trailing \r dropped.
  3. The token is not written as a host location: not ~, a drive letter, /etc, /home, /Users or /root, and no .. segment.

The file name alone never exempts anything. Everything else keeps its finding at the old severity:

  • Comments. These are free text an agent may read as instructions.
  • Negated entries such as !.env. They re-include the secrets file in the commit, build context, package or agent view.
  • Lines that are not a single pattern. Any line with whitespace, quotes, $ or shell control characters, or longer than the 4,096-character classification bound.
  • .gitattributes. Its lines attach filter, diff and merge drivers to paths rather than excluding them.
  • The same reference in any other file (SKILL.md, scripts, YAML, plain path lists), including a script that reads .env.

Tests

tests/nodes/analyzers/test_pe3_ignore_file_entries.py adds 85 synthetic tests. The 47 that cover the fix fail on main; the 38 fail-closed controls pass on both.

  • Benign:
    • the secrets section of a .gitignore;
    • 20 credential-file patterns;
    • every listed ignore file name;
    • nested paths and Windows separators;
    • CRLF and padded lines.
  • Fail-closed:
    • negations, comments, and commands or prose;
    • quotes, $HOME, host paths, Unicode separators and overlong lines;
    • the same lines in other files and in .gitattributes;
    • .env reads in Python, shell, SKILL.md and YAML.
  • Runner level and no-LLM scan level: PE3 remains only on !.env.local and on the script that reads .env.
  • make lint and make format-check pass. The unit suite (pytest -m "not integration and not provider" tests/) gives 10502 passed, 15 skipped, 4 xfailed, 0 failed.

Residual

  • An ignore file could double as a hidden list of paths that a script reads and exfiltrates. The entries themselves no longer raise PE3. The host-path guard and the exfiltration and taint analyzers still apply.
  • .gitattributes lines such as .env filter=git-crypt still raise PE3. That is a separate false positive, left out of scope.

🤖 Generated with Claude Code

…l access

PE3 matches credential file names lexically in every file type. In a
.gitignore, the entry `.env` under a "secrets" comment matched the bare
.env pattern and was reported as HIGH Credential Access. That entry keeps
the file out of version control and reads nothing. The existing .env
documentation filter only covers Markdown and text, and ignore files
infer as "other", so nothing qualified the match.

Skip a PE3 match only when all of these hold:

- The file is a gitignore-syntax exclusion list: .gitignore,
  .dockerignore and <Dockerfile>.dockerignore, .npmignore, .helmignore,
  .gcloudignore, .vercelignore, the ESLint, Prettier and Stylelint ignore
  files, .ignore, .rgignore, .fdignore, and the Cursor, Gemini and Aider
  agent ignore files.
- The whole physical line, split on "\n" as those parsers split it, is
  one pattern token that contains the match.
- The token is not written as a host location: ~, a drive letter, /etc,
  /home, /Users, /root, or a ".." segment.

Every other case keeps its finding at the old severity:

- Comments, which are free text an agent may read as instructions.
- Negated entries such as !.env. They re-include the secrets file in the
  commit, build context, package or agent view that the file governs.
- Lines with whitespace, quotes, $ or shell control characters. Unicode
  separators that the analyzer treats as line breaks count as whitespace.
- Lines longer than the 4,096-character classification bound.
- The same references in any other file: SKILL.md, scripts, YAML
  config, plain path lists, and .gitattributes. A .gitattributes line
  attaches filter, diff and merge drivers to paths; it does not exclude
  them.

Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@rng1995 rng1995 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Review: the exemption is correctly narrow. The file kind, a single-token line and a non-host path all have to hold, and negations, comments and .gitattributes stay fail-closed. I re-ran the host-path probes against main. One P3 documentation/test nit; otherwise ready.

Any basename ending in .dockerignore selects the ignore-file rules, with
or without a matching Dockerfile in the bundle. Say so next to the
suffix constant, and note that each exemption still needs a line that is
one pattern token, not a negation and not a host path.

Pin both directions in notes.dockerignore: a lone .env entry is exempt,
while a command or prose line naming .env keeps its PE3 finding.

Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant