|
| 1 | +# firstlook |
| 2 | + |
| 3 | +Know who's opening pull requests before you review them. |
| 4 | + |
| 5 | +**firstlook** is a GitHub Action that profiles PR contributors and posts a trust assessment on every pull request. It checks account age, contribution history, profile signals, commit signatures, and whether the PR touches security-critical files -- then leaves a clear summary right on the PR. |
| 6 | + |
| 7 | +The xz-utils backdoor was merged by an account that spent two years building credibility through small, legitimate contributions. GitHub's built-in "first-time contributor" label wouldn't have caught it. firstlook gives you the full picture in seconds. |
| 8 | + |
| 9 | +## Quick start |
| 10 | + |
| 11 | +Create `.github/workflows/firstlook.yml`: |
| 12 | + |
| 13 | +```yaml |
| 14 | +name: firstlook |
| 15 | +on: |
| 16 | + pull_request: |
| 17 | + types: [opened, reopened, synchronize] |
| 18 | + |
| 19 | +jobs: |
| 20 | + assess: |
| 21 | + runs-on: ubuntu-latest |
| 22 | + permissions: |
| 23 | + pull-requests: write |
| 24 | + contents: read |
| 25 | + steps: |
| 26 | + - uses: getagentseal/firstlook@v1 |
| 27 | +``` |
| 28 | +
|
| 29 | +No configuration needed. It works out of the box. |
| 30 | +
|
| 31 | +## What you get |
| 32 | +
|
| 33 | +Every PR gets a comment like this: |
| 34 | +
|
| 35 | +> ### firstlook |
| 36 | +> |
| 37 | +> | Signal | Detail | | |
| 38 | +> |--------|--------|---| |
| 39 | +> | **Account** | Created 4y 2mo ago | :white_check_mark: | |
| 40 | +> | **Repos** | 36 public | :white_check_mark: | |
| 41 | +> | **Profile** | Bio, Company, Blog | :white_check_mark: | |
| 42 | +> | **History** | 89 merged PRs elsewhere | :white_check_mark: | |
| 43 | +> | **Followers** | 142 | :white_check_mark: | |
| 44 | +> | **Signed** | Yes | :white_check_mark: | |
| 45 | +> |
| 46 | +> **Trusted** (score: 92/100) -- Established contributor with a verified presence across GitHub. |
| 47 | +
|
| 48 | +And a label like `firstlook: trusted` gets applied to the PR. |
| 49 | + |
| 50 | +When something needs attention: |
| 51 | + |
| 52 | +> ### firstlook |
| 53 | +> |
| 54 | +> | Signal | Detail | | |
| 55 | +> |--------|--------|---| |
| 56 | +> | **Account** | Created 12 days ago | :warning: | |
| 57 | +> | **Repos** | 0 public | :warning: | |
| 58 | +> | **Profile** | None provided | :warning: | |
| 59 | +> | **History** | 0 merged PRs elsewhere | :warning: | |
| 60 | +> | **Followers** | 0 | :warning: | |
| 61 | +> | **Signed** | No | :warning: | |
| 62 | +> | **Security paths** | `.github/workflows/ci.yml` | :rotating_light: | |
| 63 | +> |
| 64 | +> **Unknown Contributor** (score: 0/100) -- Very new or empty account. Touching security-critical paths -- thorough review strongly recommended. |
| 65 | + |
| 66 | +## Trust tiers |
| 67 | + |
| 68 | +| Tier | Score | Meaning | |
| 69 | +|------|-------|---------| |
| 70 | +| **Trusted** | 65+ | Established account, verified presence, broad contribution history | |
| 71 | +| **Familiar** | 40-64 | Some history and profile signals. Standard review | |
| 72 | +| **Caution** | 15-39 | Limited history or newer account. Extra scrutiny recommended | |
| 73 | +| **Unknown** | 0-14 | Very new or empty account. Thorough review needed | |
| 74 | + |
| 75 | +## Signals checked |
| 76 | + |
| 77 | +- **Account age** -- how long the GitHub account has existed |
| 78 | +- **Public repos** -- number of public repositories |
| 79 | +- **Profile completeness** -- bio, company, blog, twitter, email |
| 80 | +- **Contribution history** -- merged PRs to other repositories (excludes your repo) |
| 81 | +- **Followers** -- community recognition |
| 82 | +- **Commit signatures** -- whether commits in this PR are signed and verified |
| 83 | +- **Security-critical files** -- whether the PR touches CI, lockfiles, build scripts, or dependency manifests |
| 84 | + |
| 85 | +## Configuration |
| 86 | + |
| 87 | +All inputs are optional. Defaults work for most projects. |
| 88 | + |
| 89 | +```yaml |
| 90 | +- uses: getagentseal/firstlook@v1 |
| 91 | + with: |
| 92 | + # Token for GitHub API calls (default: automatic) |
| 93 | + github-token: ${{ secrets.GITHUB_TOKEN }} |
| 94 | +
|
| 95 | + # Paths treated as security-critical (comma-separated substrings) |
| 96 | + security-paths: '.github/workflows/,package.json,Makefile,Dockerfile,scripts/' |
| 97 | +
|
| 98 | + # Prefix for PR labels (default: firstlook) |
| 99 | + label-prefix: 'firstlook' |
| 100 | +
|
| 101 | + # Post assessment comment (default: true) |
| 102 | + post-comment: 'true' |
| 103 | +
|
| 104 | + # Apply trust-tier labels (default: true) |
| 105 | + apply-labels: 'true' |
| 106 | +
|
| 107 | + # Skip repo collaborators with write+ access (default: true) |
| 108 | + skip-collaborators: 'true' |
| 109 | +
|
| 110 | + # Usernames to always skip (comma-separated) |
| 111 | + skip-users: 'dependabot,renovate' |
| 112 | +``` |
| 113 | + |
| 114 | +## Outputs |
| 115 | + |
| 116 | +Use outputs in downstream workflow steps: |
| 117 | + |
| 118 | +```yaml |
| 119 | +steps: |
| 120 | + - uses: getagentseal/firstlook@v1 |
| 121 | + id: fl |
| 122 | + - run: echo "Tier is ${{ steps.fl.outputs.tier }}" |
| 123 | +``` |
| 124 | + |
| 125 | +| Output | Description | |
| 126 | +|--------|-------------| |
| 127 | +| `tier` | `trusted`, `familiar`, `caution`, or `unknown` | |
| 128 | +| `score` | Numeric score (0-100) | |
| 129 | +| `account-age-days` | Account age in days | |
| 130 | + |
| 131 | +### Block merges on untrusted contributors |
| 132 | + |
| 133 | +```yaml |
| 134 | + - uses: getagentseal/firstlook@v1 |
| 135 | + id: fl |
| 136 | + - name: Gate on trust tier |
| 137 | + if: steps.fl.outputs.tier == 'unknown' |
| 138 | + run: | |
| 139 | + echo "::error::PR author is an unknown contributor. Manual approval required." |
| 140 | + exit 1 |
| 141 | +``` |
| 142 | + |
| 143 | +## What it skips |
| 144 | + |
| 145 | +- **Bot accounts** -- dependabot, renovate, etc. are skipped automatically |
| 146 | +- **Repo collaborators** -- users with write, maintain, or admin access are skipped by default |
| 147 | +- **Allow-listed users** -- anyone in the `skip-users` list |
| 148 | + |
| 149 | +## Built by |
| 150 | + |
| 151 | +[AgentSeal](https://agentseal.org) -- security tooling for the AI agent ecosystem. |
| 152 | + |
| 153 | +Born from maintaining [CodeBurn](https://github.com/getagentseal/codeburn), where every PR from an unknown account meant 5 minutes of manual profile checking. firstlook automates the first 60 seconds of that process. |
| 154 | + |
| 155 | +## License |
| 156 | + |
| 157 | +MIT |
0 commit comments