See Design Documentation for a comprehensive description of all actors, actions, and data flows within the system.
See Threat Model for a systematic threat model identifying trust boundaries, critical code paths, and a STRIDE analysis of potential attacks with their mitigations.
Please email chicks.net@gmail.com with a subject line like "$REPONAME SECURITY issue: $SUMMARY". Details on when you experienced the issue, logs, and other context are appreciated to assist with effective triaging of your issue.
gh-observer follows a rolling release model. Each new release includes the latest features, bug fixes, and security updates. We support the most recent release only — there are no long-term support (LTS) branches or parallel maintenance streams.
The scope of support for each release includes:
- Security vulnerability fixes
- Bug fixes that affect core functionality (PR check watching, TUI rendering, API interaction)
- Compatibility updates for GitHub API changes and Go version requirements
We do not backport fixes to older releases. Users should upgrade to the latest release to receive all fixes.
A release version no longer receives security updates as soon as a newer release is published. There is no fixed support window — the only supported version is the latest release at any given time.
If a security vulnerability is reported, we will fix it in the next release. We do not issue patches for older versions. Users must upgrade to the latest release to obtain security fixes.
End users should expect new releases to include any security updates and there should be a notification in the release notes. We may participate in other disclosure programs as circumstances may warrant.
There are no known security vulnerabilities in the software at the time this was written.
We are unaware of any security risks particular to this software that you should be aware of. Please let us know if we missed anything or forgot to update this section in too long.
While the project is active, any vulnerabilities in software components that do not affect the project are accounted for in a VEX document, augmenting the vulnerability report with non-exploitability details. gh-observer uses OpenVEX to publish machine-readable statements about which vulnerabilities are not exploitable in this project.
VEX documents are generated automatically from govulncheck call-graph
analysis, which determines whether vulnerable code paths in dependencies are
actually reached by gh-observer:
-
Automated generation: The
vex/generate-vex.shscript runsgovulncheck -jsonand converts findings into OpenVEX format. Vulnerabilities that govulncheck determines are not called by the project are marked asnot_affectedwith the justificationvulnerable_code_not_in_execute_path. -
CI workflow: The
.github/workflows/vex.ymlworkflow regenerates the VEX document weekly (Mondays at 10:30 UTC), on every release, and on demand viaworkflow_dispatch. The resulting document is uploaded as a workflow artifact with 90-day retention. -
Manual additions: Maintainers can add manual VEX statements for vulnerabilities that require human analysis beyond what govulncheck can determine automatically:
just vex_add CVE-2025-12345 "vulnerable_code_not_in_execute_path" \ "govulncheck shows this is not called"
- Repository:
vex/openvex.jsonin the repository root - CI artifacts: Uploaded as
openvex-documentartifact by the VEX workflow - Format: OpenVEX v0.2.0
VEX statements in gh-observer use the following OpenVEX justifications:
| Justification | When Used |
|---|---|
vulnerable_code_not_in_execute_path |
govulncheck determines the vulnerable code is imported but no call path from gh-observer reaches it |
vulnerable_code_not_present |
The vulnerable code has been removed or patched in the dependency version used |
inline_mitigations_already_exist |
gh-observer includes its own mitigations that prevent exploitation |
component_not_present |
The vulnerable component is not included in gh-observer's build |
# Generate or update the VEX document
just vex_generate
# View the current VEX document
just vex_show
# Add a manual not_affected statement
just vex_add CVE-2025-12345 "vulnerable_code_not_in_execute_path" \
"govulncheck shows this is not called"This policy defines the thresholds for remediating Software Composition Analysis (SCA) findings related to vulnerabilities and licenses in our dependencies.
Dependencies with known vulnerabilities are triaged by severity and exploitability using the following thresholds:
| Severity | Remediation Timeline | Method |
|---|---|---|
| Critical (CVSS 9.0+) | Within 48 hours | Immediate dependency update or VEX justification |
| High (CVSS 7.0–8.9) | Within 7 days | Dependency update in next working session; VEX statement if not exploitable |
| Medium (CVSS 4.0–6.9) | Within 30 days | Tracked via Dependabot alert; addressed in next dependency update cycle |
| Low (CVSS 0.1–3.9) | Within 90 days | Addressed during regular maintenance; VEX statement if not exploitable |
For all vulnerability levels, if govulncheck call-graph analysis
determines the vulnerable code path is not reached by gh-observer, the
finding is documented as not_affected in the VEX document (see
VEX section above) per the
justifications defined in that section. VEX statements satisfy the
remediation requirement — no further action is needed once a finding is
justified and documented.
-
Automated detection:
govulncheckruns on every push tomain, every pull request, and daily via the govulncheck workflow. GitHub Dependabot provides continuous monitoring for new vulnerability advisories. -
Triage: When a vulnerability is detected, it is assessed using
govulncheckcall-graph analysis to determine if the vulnerable code is actually called. If not called, a VEXnot_affectedstatement is generated. -
Remediation: If the vulnerable code is called, the dependency is updated within the timeline above. If no patched version exists, a mitigation is implemented or the dependency is replaced.
-
Verification: The VEX document is regenerated weekly and on every release to ensure all statements remain current.
just vex_generatecan be run manually at any time.
All direct and indirect dependencies must meet the following license requirements:
| License Category | Allowed | Action if Non-Compliant |
|---|---|---|
| Permissive (Apache-2.0, MIT, BSD-2/3-Clause, ISC, MPL-2.0, etc.) | Yes | No action needed — these are compatible with the project's GPLv2 license |
| Weak copyleft (LGPL-2.0+, LGPL-2.1, MPL-2.0) | Yes, with review | Review usage pattern to ensure compliance (dynamic linking, etc.) |
| Strong copyleft (GPLv3, AGPL) | Review required | Evaluate compatibility with GPLv2; replace dependency if incompatible |
| Proprietary / no license | No | Remove dependency; find a permissively-licensed alternative |
License compliance is verified by:
go modensures all dependencies declare agodirective and are fetchable from public proxies.- The dependency-review workflow flags incompatible or unknown licenses on every pull request.
- Manual review during dependency adoption (see How We Select Dependencies).
- Dev-only dependencies (linters, test tools) that are not included in the release binary may use any permissive or weak-copyleft license.
- If no suitable alternative exists for a non-compliant dependency, the maintainer documents the exception and the justification in this section.
This policy defines the thresholds for remediating Static Application Security Testing (SAST) findings from code analysis and configuration scanning tools.
| Tool | Scope | Trigger |
|---|---|---|
| CodeQL | Go source code security vulnerabilities and coding errors | Push to main, PRs to main, weekly schedule (Monday 00:00 UTC) |
| Checkov | Infrastructure-as-code and configuration misconfigurations | Push to main, PRs to main, manual dispatch |
| golangci-lint | Go static analysis (pre-commit hook) | Every commit |
SAST findings are triaged by severity using the following thresholds:
| Severity | Remediation Timeline | Method |
|---|---|---|
| Critical | Within 48 hours | Immediate fix or disabling the affected code path; hotfix release if needed |
| High | Within 7 days | Fix in next working session; PR must address before merge |
| Medium | Within 30 days | Tracked via GitHub code scanning alert; addressed in next maintenance cycle |
| Low | Within 90 days | Addressed during regular maintenance; dismissed with documented justification if not applicable |
-
Automated detection: CodeQL runs on every push to
main, every pull request, and weekly via the CodeQL workflow. Checkov runs on every push tomain, every pull request, and on demand via the Checkov workflow. Both upload results to GitHub code scanning. -
Triage: When a SAST finding is detected, it is assessed for accuracy and applicability. False positives are dismissed with a documented justification in the GitHub code scanning alert. True positives are classified by severity and assigned for remediation.
-
Remediation: The finding is fixed within the timeline above. For CodeQL findings, this typically involves modifying the affected source code. For Checkov findings, this typically involves updating configuration files. If a finding is a true positive but cannot be fixed immediately, a mitigation is documented in the alert and the finding is tracked to closure.
-
Verification: After remediation, the next CI run confirms the finding is resolved. CodeQL findings that reappear after dismissal are re-triaged rather than automatically re-dismissed.
SAST tools may produce false positives. A finding may be dismissed only with a documented justification in the GitHub code scanning alert. Acceptable justifications include:
- Not applicable: The code path is unreachable in production or the vulnerability pattern does not apply to the project's usage.
- False positive: The tool incorrectly flagged code that is not vulnerable.
- Risk accepted: The finding is acknowledged but the risk is accepted based on project context (requires explicit maintainer approval).
Dismissed findings are reviewed quarterly to ensure the justification remains valid.
This policy covers SAST findings from CodeQL (Go source code) and Checkov (configuration and IaC). Dependency vulnerability findings are handled under the SCA Remediation Threshold Policy above. Secret scanning findings are handled under Secrets and Credentials Management.
When the project has made a release, the project documentation MUST include a description of how the project selects, obtains, and tracks its dependencies.
- Dependencies are chosen based on necessity, maturity, and maintenance activity.
- We prefer well-maintained libraries with active maintainers and transparent security practices.
- We evaluate dependencies for compatibility, license compliance, and security history before adoption.
- All dependencies are declared in
go.modand fetched viago get/go mod download. - The
go.sumfile tracks checksums to ensure integrity and reproducibility. go mod tidyis used to keep the dependency graph minimal and accurate.
go.sumprovides cryptographic verification of all downloaded modules.- We use
just deps-updateto regularly update dependencies and apply security patches. - Pre-commit hooks include
gitleaksfor secret scanning andgolangci-lintfor static analysis. - GitHub Dependabot is enabled to automatically flag and propose updates for vulnerable dependencies.
When the project has made a release, the project documentation MUST include instructions to verify the integrity and authenticity of the release assets. All release binaries are signed and attested using three complementary supply chain security mechanisms:
- GitHub Build Attestations - Verifiable via
gh attestation verify - Cosign Keyless Signatures - Verifiable via
cosign verify-blob - SLSA Provenance - Verifiable via
slsa-verifier
See Verifying Release Assets in the README for complete step-by-step verification instructions.
When the project has made a release, the project documentation MUST contain instructions to verify the expected identity of the person or process authoring the software release. Releases are authored through an automated process; the following methods verify that identity.
Releases are created by just release, which runs gh release create using
the GitHub CLI authenticated as a repository maintainer. The release workflow
(.github/workflows/release.yml) then builds and signs the binaries. You can
verify that a release was produced by this authorized process:
-
Check the release workflow run: Every release triggers a GitHub Actions workflow run. Visit the release page on GitHub and follow the link to the workflow run that produced the binaries. Confirm the workflow file path is
.github/workflows/release.ymland the triggering event is a tag push. -
Verify build attestations: GitHub build attestations cryptographically bind each binary to the specific workflow and repository that built it:
gh attestation verify darwin-arm64 --owner fini-net
This confirms the binary was built by the
fini-net/gh-observerrelease workflow, not by an external or unauthorized process. -
Verify SLSA provenance: The
.intoto.jsonlprovenance attestation provides non-forgeable proof of the build origin, including the source repository, branch, and builder identity. See Option 3: SLSA Provenance for verification commands.
Every commit in the repository includes a Signed-off-by: trailer per the
DCO policy (see Contributor Legally Authorized Assertion
below). To verify the identity of the person who authored the release:
-
Check the release tag commit: View the commit the release tag points to and verify the author and committer identity:
git log -1 --format='Author: %an <%ae>%nCommitter: %cn <%ce>%nSigned-off-by: %(trailers:key=Signed-off-by)' v1.8.0 -
Check the GitHub release author: On the releases page, each release shows the GitHub username of the maintainer who created it. This should match an authorized repository maintainer.
-
Cross-reference DCO signatures: The
Signed-off-by:trailer on each commit in the release range affirms the contributor's legal authorization under the DCO. Verify the trailer identity matches the commit author.
This project defines the following policy for storing, accessing, and rotating secrets and credentials used in development, CI/CD, and release processes.
| Secret | Purpose | Storage |
|---|---|---|
GITHUB_TOKEN |
Auto-provisioned per-workflow token for GitHub Actions | GitHub Actions runtime (automatic) |
CLAUDE_CODE_OAUTH_TOKEN |
OAuth token for Claude Code Action | GitHub repository secret |
SCORECARD_TOKEN |
(Optional) PAT for OpenSSF Scorecard write access | GitHub repository secret (currently unused) |
- No secrets in source code: Secrets must never be committed to the repository, including hardcoded values in code, configuration files, or documentation.
- GitHub Secrets for CI/CD: All secrets used in GitHub Actions workflows are stored as GitHub repository secrets, which are encrypted at rest and never exposed in logs.
- No local secret files: Developers must not store credentials in
unencrypted files within the repository (e.g.,
.envfiles). The application retrieves the GitHub token at runtime viaGITHUB_TOKENenvironment variable orgh auth token— never from a file on disk.
- Least-privilege permissions: Every workflow declares explicit
permissions:blocks scoped to the minimum required. No workflow usespermissions: write-allor broadcontents: writeunless necessary. - No persistent credentials in checkout: All
actions/checkoutsteps usepersist-credentials: falseto prevent the GITHUB_TOKEN from persisting on disk after the checkout step completes. - No secret sharing across workflows: Each workflow references only the secrets it needs. Secrets are not passed between jobs or workflows unless required by the workflow's purpose.
- Environment protection: The Claude Code workflow runs in the
claudeGitHub environment, providing an additional access gate.
GITHUB_TOKEN: Automatically rotated by GitHub Actions on every workflow run. No manual rotation required.CLAUDE_CODE_OAUTH_TOKEN: Must be rotated by a repository administrator when the token expires, is suspected of compromise, or when the associated OAuth grant is revoked. Set a calendar reminder to review quarterly.- Emergency rotation: If any secret is suspected of compromise, a repository administrator must revoke and replace it immediately via GitHub repository settings, then file a security issue per the Confidential Disclosure process.
- Pre-commit scanning:
gitleaksruns as a pre-commit hook to detect accidentally committed secrets before they reach the repository. - GitHub secret scanning: GitHub's built-in secret scanning is enabled on the repository to detect leaked tokens in pushed commits and PRs.
- Workflow hardening: All workflows use pinned SHA references for third-party
actions (not tags),
step-security/harden-runnerfor egress auditing, andpersist-credentials: falseon checkout steps.
When the project has made a release, CI/CD pipelines which accept trusted collaborator input MUST sanitize and validate that input prior to use in the pipeline.
The following table identifies all inputs accepted by CI/CD pipelines from trusted collaborators (repository members, PR authors, commenters) and describes the validation applied:
| Input | Source | Workflow | Validation |
|---|---|---|---|
github.repository |
GitHub (workflow context) | claude-code-review.yml |
Regex validated against ^[a-zA-Z0-9_-]+/[a-zA-Z0-9_.-]+$ in explicit validation step before use |
github.event.pull_request.number |
GitHub (PR event) | claude-code-review.yml |
Validated as positive integer (^[1-9][0-9]*$) in explicit validation step before use |
github.event.comment.body |
Commenters | claude.yml |
Not interpolated into shell or action inputs; only used in contains() expression for trigger filtering. Author association checked to restrict to owner, member, or collaborator |
github.event.review.body |
Reviewers | claude.yml |
Same as comment body — contains() filter only, author association checked |
github.event.issue.body, github.event.issue.title |
Issue authors | claude.yml |
Same as comment body — contains() filter only, author association checked |
github.event.pull_request.number |
GitHub (PR event) | claude.yml |
Not used in workflow (action detects PR context internally) |
| PR code changes | Fork contributors | All PR-triggered workflows | All PR workflows use pull_request trigger (not pull_request_target), running in fork context without secret access |
-
No
pull_request_targetusage: All workflows that trigger on PRs use thepull_requestevent, which checks out the fork's code and runs without access to repository secrets. This prevents untrusted PR code from exfiltrating secrets. -
Author association gating: The
claude.ymlworkflow restricts execution to users withowner,member, orcollaboratorauthor association. This prevents arbitrary commenters on public repositories from triggering the Claude Code action with secret access. -
Explicit validation steps: Before event-sourced values like
github.repositoryor PR numbers are interpolated into action inputs, they pass through a dedicatedValidate PR inputsstep that checks them against expected patterns. Only validated outputs (steps.validate.outputs.*) are used in subsequent steps. -
No
${{ }}interpolation of untrusted strings inrun:steps: No workflow uses${{ }}expressions to interpolate untrusted event data (PR titles, bodies, comment text, etc.) directly into shell commands. Allrun:steps use static commands or trusted environment variables. -
Pinned action SHAs: All third-party actions are referenced by commit SHA (not mutable tags), preventing tag-mutation supply chain attacks.
-
Environment protection: Comment-triggered workflows (
claude.yml,claude-code-review.yml) run in theclaudeGitHub environment, which provides an additional approval gate before secrets are available. -
Harden-runner egress auditing: Most workflows use
step-security/harden-runnerwithegress-policy: auditto detect unexpected outbound network calls that could indicate data exfiltration.
| Threat | Mitigation |
|---|---|
Script injection via PR title/body in run: step |
No run: step interpolates PR-sourced data |
| Secret exfiltration via fork PR code | pull_request trigger runs in fork context; no pull_request_target |
| Arbitrary user triggering Claude Code action | author_association check restricts to repo collaborators |
| Malicious action version via tag mutation | All actions pinned to SHA hashes |
| Credential persistence after checkout | All actions/checkout steps use persist-credentials: false |
Malicious input via github.repository or PR number |
Explicit validation step with regex/integer checks |
This project requires that code collaborators be reviewed prior to receiving escalated permissions to sensitive resources.
- No elevated permissions for external collaborators: Write, admin, and secret access are not granted to external contributors.
- Fork-based contributions: All external contributions are submitted via pull requests from personal forks. No direct push access to protected branches is granted to anyone outside the maintainer team.
- Least-privilege principle: CI workflows use scoped
permissions:blocks andpersist-credentials: falseon checkout steps (see Secrets and Credentials Management).
If elevated permissions (write access, admin access, or access to repository secrets) are ever considered for a collaborator, the following policy applies:
- The request must be reviewed by an existing repository maintainer.
- Permissions are granted at the minimum scope necessary for the collaborator's role.
- Elevated permissions are time-limited where GitHub supports it, or reviewed quarterly otherwise.
- When a collaborator's role changes or they become inactive, their elevated permissions are revoked promptly.
The version control system requires all code contributors to assert that they are legally authorized to make the associated contributions on every commit. This is enforced through the Developer Certificate of Origin (DCO) policy described in CONTRIBUTING.md.
Every commit must include a Signed-off-by: trailer (added automatically
by git commit -s), which affirms that the contributor agrees to the DCO
v1.1 terms. Pull requests missing this trailer on any commit are blocked by
the DCO GitHub App and cannot be merged.