Skip to content

feat: protected files check for agent-created PRs#1

Merged
raykao merged 12 commits into
mainfrom
feat/protected-files
Apr 16, 2026
Merged

feat: protected files check for agent-created PRs#1
raykao merged 12 commits into
mainfrom
feat/protected-files

Conversation

@raykao

@raykao raykao commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

What changed

Adds a new protected files phase to the pipeline (Phase 2, between constraint validation and secret sanitization). When an agent creates a PR that modifies sensitive file paths, the action blocks or warns based on configuration.

New pipeline

Validate → Protected Files (NEW) → Sanitize → Threat Detect → Execute

Features

  • Pattern matching: .gitignore-compatible globs via picomatch with { dot: true }
  • Built-in defaults: CI configs (.github/workflows/**), dependency manifests (**/package.json, **/go.mod, etc.), agent instructions (AGENTS.md, .claude/**, .codex/**), access control (CODEOWNERS)
  • Monorepo support: Manifest patterns use **/ prefix to match at any directory depth
  • Config resolution: Action input patterns merged after built-in defaults. !pattern negation creates exceptions (last match wins). override-defaults: true to opt out entirely.
  • Two modes: block (hard fail) and warn (annotate but continue)
  • Path normalization: path.posix.normalize() prevents bypass via ./, ../, //. Path traversal attempts are rejected as security violations.
  • Category classification: Violations are labeled by category (CI config, Dependency manifest, Agent instructions, Access control, Custom)

New action inputs

Input Default Description
protected-files '' Glob patterns (one per line), merged with defaults
protected-files-action block block or warn
protected-files-override-defaults false Skip built-in defaults

Design decisions

Full analysis in research/protected-files-pattern-syntax.md and research/gh-aw-agentic-workflows.md.

  • picomatch over minimatch: zero deps, ReDoS protection, ncc-friendly
  • Merge with negation over replace: safe defaults floor with surgical exceptions
  • **/ prefix for manifests: security control should match at any depth

Review-fix loop

  • Cycle 1: 3 High, 1 Medium found (warn mode swallowed violations, path normalization bypass, input validation gap, root-only manifest matching)
  • Cycle 2: All 4 fixed, 0 issues remaining

Test evidence

  • 107 tests total (69 in protected-files suite), all passing
  • Coverage: 100% statements/functions/lines on protected-files.ts, 97.4% branches
  • False positives checked: 6 test paths traced; all confirmed genuine
  • npm run all passes (format, lint, test, build)

Relates to: gh-aw gap analysis Priority 1

raykao and others added 12 commits April 15, 2026 11:44
Add picomatch for glob pattern matching in protected files feature.
Also adds @types/picomatch for TypeScript support.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add src/protected-files.ts with:
- Default protected patterns for CI config, dependency manifests,
  agent instructions, and access control files
- gitignore-style pattern matching via picomatch (last match wins,
  negation with ! prefix)
- Config resolution merging built-in defaults with user patterns
- Category classification for violation reporting
- checkProtectedFiles scans all create_pull_request actions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add Phase 2 (protected files check) between validation and sanitization
- Read new inputs: protected-files, protected-files-action,
  protected-files-override-defaults
- Block or warn when protected files are modified in create_pull_request
- Renumber subsequent phases (sanitization -> 3, threats -> 4, exec -> 5)
- Add three new inputs to action.yml with descriptions and defaults

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add 57 tests covering:
- Default pattern verification (CI config, deps, agent instructions, access control)
- Custom patterns extending defaults
- Negation patterns creating exceptions
- Override defaults mode (only user patterns apply)
- Actions without files are skipped
- Non-PR actions pass through
- Mixed action types (only create_pull_request checked)
- Category classification correctness
- Warn mode (violations reported but passed)
- Block mode (violations cause failure)
- Glob pattern specifics (** vs *, nested paths)
- Dotfile matching (.github/, .claude/)
- Safe file pass-through (src/index.ts, README.md)
- Multiple violations in single PR
- Last-match-wins pattern ordering
- Empty/whitespace pattern filtering

Also fixes isFileProtected to skip empty patterns gracefully
(picomatch throws on empty strings).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rebuilt with ncc after adding protected files feature.
Includes picomatch dependency bundled into dist/index.js.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The violation logging gate checked !protectedResult.passed, but
checkProtectedFiles sets passed=true in warn mode regardless of
violations. This meant warnings were never emitted. Changed the
gate to check protectedResult.violations.length > 0 so both
block errors and warn annotations are surfaced.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Agent output paths like ./package.json, sub/../CODEOWNERS, or
.github//workflows//ci.yml could bypass picomatch patterns while
Git API would normalize them to protected locations. Now paths
are normalized with path.posix.normalize before matching, and
paths that escape the repo root (../ or /) are flagged as
security violations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The input was cast with "as block | warn" which provides no
runtime protection. A typo like "Block" or "warnings" silently
disables blocking. Now the input is validated at startup and
the action fails fast with a clear message for invalid values.
The unsafe type cast is removed since TS narrows the type
after the guard clause.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Dependency manifest patterns like package.json only matched at
the repo root. In monorepos, apps/web/package.json would bypass
protection. Updated all manifest patterns to use **/ prefix so
they match at any depth. Updated CATEGORY_MAP to match.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nd monorepo depth

Added test suites:
- path normalization: ./ prefix, ../ traversal, // double slashes
- path traversal rejection: ../ escape, absolute paths, normalized escape
- monorepo depth matching: nested package.json, go.mod, all manifests
- warn mode emits violations: violations array populated in warn mode

Updated existing tests to use **/ prefix for dependency manifest
patterns to match the new defaults.

69 tests passing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rebuilt after all four security fixes (warn mode gate, path
normalization, input validation, monorepo depth matching).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@raykao raykao merged commit de4e7dd into main Apr 16, 2026
1 check passed
@raykao raykao deleted the feat/protected-files branch April 16, 2026 01:35
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