signals

command module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 6, 2026 License: MIT Imports: 41 Imported by: 0

README

signals

A command for tracing security-relevant evidence stored in Git across repository history. It shows when detected evidence appears, changes, or disappears, with the commit and path behind each change. signals walks commit trees with github.com/git-pkgs/history. It combines nine raw detectors from OpenSSF Scorecard v5.6.0 with git-pkgs modules that detect CI and security tools, secrets, REUSE metadata, package managers, manifests, lockfiles, dependency pinning, and update configuration. Commit data adds dependency-bot and signature events plus contributor, maintenance, review, and signing metrics. The output contains the evidence and its provenance without calculating a score. The scan reads local Git objects, with forge APIs, release APIs, credential validation, and external services disabled.

The local-only timeline answers questions about when repository evidence changed. Examples include:

  • What was the complete detected state before and after a grant-funded period?
  • When was a security policy, CI system, or security tool added or removed?
  • Which commit introduced an unpinned GitHub Action or container image?
  • How have workflow token permissions changed over time?
  • When did a package manager, manifest, lockfile, or integrity record appear?
  • How did Dependabot or Renovate configuration and schedules change?
  • Did a credential enter the history, and when was it removed from the tree?
  • How did licensing coverage, contributor activity, review proxies, and commit signing change?

Repository evidence and limits

Git retains deleted configuration, earlier licensing coverage, embedded commit signatures, canonical author identities, and the exact commit that introduced or removed a file-backed practice. The same local scan works across hosting services and avoids API rate limits. Blob IDs and changed paths allow results to be reused across thousands of commits.

Some security state exists only on the hosting service or in an external dataset. Branch protection, webhooks, issue activity, successful CI runs, release assets, vulnerability databases, and external registries require separate sources. Review patterns in commit metadata do not prove that a pull request was approved, signature presence does not prove verification, and recent commits do not show whether a repository is marked as archived. Removing a credential from the tree does not remove its blob from Git history or show that it was revoked. Scorecard remains useful for current forge configuration and external evidence, while signals supplies the repository history those sources cannot reconstruct.

Install

The project requires Go 1.27.3 or later. Install the latest version with:

go install github.com/git-pkgs/signals@latest

To work from source, build the current checkout directly. This writes the binary beside the source files:

go build -o signals .

Quick start

Run signals log inside a Git repository, or pass another local repository as the final argument. History readers can work concurrently, while output remains in commit order:

./signals log --workers 8 .
./signals log --workers 8 --ref v1.0.0 .
./signals log --workers 8 --since v1.0.0 .
./signals log --workers 8 /path/to/repository

Date bounds make it possible to inspect changes around an event such as grant funding. --after includes the named UTC committer date, while --before excludes it:

./signals log --workers 8 --jsonl --snapshots \
  --after 2024-01-01 \
  --before 2025-01-01 \
  /path/to/repository > grant-window.jsonl

The scan still computes signal state before the lower bound. A removal within the selected dates can therefore refer to a signal added before the funding date. With --snapshots, the first JSONL record contains the complete signal set and 90-day commit metrics at the last first-parent commit before --after; the final record does the same for --before.

Flags must appear before the repository path. The timeline is written to stdout, while progress, detector errors, and optional statistics are written to stderr:

./signals log --workers 8 --progress 1000 --stats /path/to/repository \
  > /tmp/signals.log \
  2> /tmp/signals.stats

The log command accepts these options. Run signals log --help to print the same list locally:

--ref REVISION   revision to walk (default HEAD)
--since REVISION exclude this revision and its ancestors
--after DATE     include commits on or after YYYY-MM-DD
--before DATE    include commits before YYYY-MM-DD
--workers N      concurrent history readers (default 1)
--progress N     report progress every N commits
--stats          report detector and blob-cache statistics
--jsonl          write JSON Lines change, event, and snapshot records
--snapshots      emit signal snapshots at both date boundaries

Output

Each block names a commit where the detected state changed or commit-local evidence was found. A + line adds a signal, a - line removes the previous one, and a * line records an event:

15dfa527561f  2016-12-18  Add support for Docker Compose development environment
  + Pinned-Dependencies/containerImage ruby at Dockerfile = unpinned

62308ad45a2d  2026-02-10  build(deps): Bump actions/checkout from 6.0.1 to 6.0.2
  * Dependency-Update-Tool/commit Dependabot
  * Signed-Commit/openpgp
  + Pinned-Dependencies/GitHubAction actions/checkout at .github/workflows/release.yml = pinned@de0fac2e4500dabe0009e67214ff5f5447ce83dd
  - Pinned-Dependencies/GitHubAction actions/checkout at .github/workflows/release.yml = pinned@8e8c483db84b4bee98b60c0593521ed34d9990e8

Non-merge commits are compared with their first parent. Merge snapshots are scanned and cached so later commits inherit the right state. Merge commits omit tree-state changes but can produce commit events.

JSONL

--jsonl writes structured change and event records. Each record contains the full commit hash, author and committer identities and timestamps, and subject:

{"type":"change","commit":"cb52a20b85fc2af8f7e6db0bc2cac843735eff49","author":{"name":"dependabot[bot]","email":"49699333+dependabot[bot]@users.noreply.github.com"},"committer":{"name":"GitHub","email":"noreply@github.com"},"authored_at":"2026-09-07T23:55:23Z","committed_at":"2026-09-08T00:02:11Z","subject":"build(deps): Bump zizmorcore/zizmor-action from 0.6.2 to 0.6.3","changes":[{"change":"added","check":"Pinned-Dependencies","kind":"GitHubAction","name":"zizmorcore/zizmor-action","value":"pinned@70fb788f84895a7701f5643d103d587e460b5c99","path":".github/workflows/zizmor.yml"},{"change":"removed","check":"Pinned-Dependencies","kind":"GitHubAction","name":"zizmorcore/zizmor-action","value":"pinned@3dc1ecc9bcb9e94e9b2c709687979e1298497054","path":".github/workflows/zizmor.yml"}]}
{"type":"event","commit":"cb52a20b85fc2af8f7e6db0bc2cac843735eff49","author":{"name":"dependabot[bot]","email":"49699333+dependabot[bot]@users.noreply.github.com"},"committer":{"name":"GitHub","email":"noreply@github.com"},"authored_at":"2026-09-07T23:55:23Z","committed_at":"2026-09-08T00:02:11Z","subject":"build(deps): Bump zizmorcore/zizmor-action from 0.6.2 to 0.6.3","events":[{"check":"Dependency-Update-Tool","kind":"commit","name":"Dependabot"},{"check":"Signed-Commit","kind":"openpgp"}]}

Change records have type set to change. Commit-local evidence uses type: event and an events array. Boundary records use type: snapshot, identify the start or end boundary, and contain the full detected state in signals plus commit-stream measurements in metrics. Ratios retain their numerator and denominator. history_complete is false for shallow or --since scans, and mailmap_oid identifies the .mailmap blob used to normalize contributor identities. Detector failures remain on stderr and also appear in an errors array when their state changes on an emitted commit. Every object occupies one physical line, including values containing escaped newlines.

Commit events cover dependency updates authored by Dependabot, Renovate, Scala Steward, or Greenkeeper and the presence of OpenPGP, SSH, X.509, or unclassified commit signatures. Signature presence does not imply successful verification.

Snapshot metrics use the preceding 90 days. They report non-bot contributor and email-domain counts, commit activity, review proxies, and signed-commit presence. Review proxies include pull-request merge and squash subjects, differing author and committer identities, and Reviewed-by, Acked-by, or Tested-by trailers. Signed-off-by remains separate because it records sign-off rather than review.

Detected signals

The command runs nine Scorecard checks. Several retain the repository-backed part of a check because Scorecard combines local files with remote evidence.

Check Local evidence Remote evidence omitted
Binary-Artifacts Binary files identified by content or extension Successful Gradle wrapper-validation runs
Dangerous-Workflow Script injection and untrusted checkout patterns in Actions workflows None
Dependency-Update-Tool Dependabot, Renovate, and Scala Steward configuration Dependabot-authored commit search
Fuzzing ClusterFuzzLite configuration and source patterns selected by the languages in each commit OSS-Fuzz membership
License Root license filename detection Forge license classification
SAST Known Actions and Sonar configuration in pom.xml Successful check runs
SBOM Root source SBOM filenames Release assets
Security-Policy Policy files and their contact or disclosure text Inherited policy from the organization .github repository
Token-Permissions Top-level and job-level Actions permissions None

Unsupported client methods return Scorecard's ErrUnsupportedFeature, and the scan performs no network requests. Detector errors are reported on stderr. The previous successful state for that check remains in place until detection succeeds again.

The additional detectors use the same persistent signal model:

Check Local evidence
CI CI configuration detected by the Brief knowledge base, including GitHub Actions, GitLab CI, Azure Pipelines, CircleCI, Jenkins, Buildkite, Drone, and Travis CI
Build Tools classified as build tools by Brief, including Make, CMake, Meson, Bazel, Buck, and language-specific build tools
Security-Tool Security tools detected by Brief, with its taxonomy function retained as the signal kind
Secrets-Management SOPS configuration
Pre-Commit pre-commit, Lefthook, Husky, and Overcommit configuration
Dependency-Update-Config Dependabot and Renovate configuration, including package managers, directories, schedules, cooldowns, groups, and semantic configuration changes
Package-Manager The selected package manager for each detected package ecosystem and project directory
Package-File Manifest and lockfile presence, including files recognized by package-manager definitions before dependency parsing is available
Dependency-Constraint Ranged, unparsed, and unversioned direct dependency constraints as written in parsed manifests
Dependency-Pin Exact direct dependency constraints found in parsed manifests
Dependency-Pinning Exact, ranged, unparsed, and resolved dependency counts from manifests and lockfiles
Lockfile-Integrity Dependencies with declared integrity metadata compared with the total entries in a lockfile
Pinned-Dependencies Repository-based uses steps pinned to commit hashes and container images pinned to SHA-256 digests, parsed from changed workflows, Dockerfiles, and Compose files
Package-Parse Package files that were identified but could not be parsed
Secret Findings from the embedded git-pkgs/secrets rule corpus; output contains [redacted] in place of the credential
REUSE Per-file SPDX license declarations, REUSE.toml or DEP5 metadata, and the number of non-empty covered files with both license and copyright information

History scanning

The root commit receives a full scan. Later commits reuse their first parent's signal state and rerun a detector only when relevant paths change. Checks with cross-file behavior, including License, SBOM, Security-Policy, and dependency-update configuration, receive a full commit tree when one of their inputs changes.

File content is cached by Git blob OID for the whole run. Unchanged bytes are read once even when they appear in many commits or several detectors request them. Commit reading can run concurrently, while signal-state updates remain ordered. Fuzzing keeps a language inventory for each commit using github.com/git-pkgs/languages. Unambiguous extensions need no content read; ambiguous extensions use a cached 4 KB prefix. github.com/git-pkgs/roles removes vendored, generated, fixture, cache, and build-output paths from that inventory and from the files scanned for fuzzing patterns. Test paths remain eligible because fuzz targets commonly use them.

Brief reads commit trees through history.TreeFS and reruns only when Brief's changed-file filter identifies an input to one of the emitted tool signals. Package files are parsed when their paths change, and the parse result supplies dependency constraints, pins, Actions references, container images, lockfile resolution, and integrity records. Results are cached by blob OID and path. Shell commands that download or install packages are not yet included in this local package detector. Package-manager detection uses managers.AffectedDirectories to rescan only the affected projects. Secret scanning updates changed paths and caches results by blob OID and path because some rules depend on the filename. REUSE metadata changes trigger a full REUSE scan; ordinary changes update the affected file state and coverage count.

The Scorecard dependency brings a large set of transitive packages and repeatedly parses the same files for related checks. Existing git-pkgs modules already cover several parts of the scan:

A shared Actions workflow parser could remove the largest remaining duplication. One parsed workflow could feed Dangerous-Workflow, reusable-workflow and script pinning, SAST, Token-Permissions, Packaging, and Gradle wrapper validation.

Development

Run the tests with the standard Go command. The suite covers the CLI boundary and historical state changes:

go test ./...

License

MIT.

Documentation

The Go Gopher

There is no documentation for this package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL