Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7240037
feat: expand detector coverage and fix regex patterns
pixincreate Jul 28, 2026
e5cf31d
fix: add CRITICAL severity support (was silently downgraded to LOW)
pixincreate Jul 28, 2026
16d1072
feat: add baseline suppression for known findings
pixincreate Jul 28, 2026
4a2bbdc
fix: resolve merge conflicts and restore full feature implementation
pixincreate Jul 29, 2026
2c421c8
chore: put omo in gitignore
pixincreate Jul 29, 2026
beee475
fix: scan_stream chunk overlap, salted SHA-256 baseline, real stdin i…
pixincreate Jul 29, 2026
82abd35
feat: add GitHub Action composite action and Dockerfile
pixincreate Jul 30, 2026
2e62f03
fix: address PR review issues - remove eval, add --locked, non-root u…
pixincreate Jul 30, 2026
6e104bd
docs(changelog): add PR review fix entries
pixincreate Jul 30, 2026
6c1e335
docs(changelog): add GitHub Action and Docker entries
pixincreate Jul 30, 2026
cf8fded
Merge remote-tracking branch 'origin/master' into feat/missing-features
pixincreate Jul 30, 2026
ba70d14
Merge remote-tracking branch 'origin/feat/missing-features' into feat…
pixincreate Jul 30, 2026
b6dfa24
chore: bump GitHub Actions to latest versions, minimize Docker image …
pixincreate Jul 31, 2026
8019783
refactor: use const for baseline version and domain separator, rename…
pixincreate Jul 31, 2026
916d591
Merge remote-tracking branch 'origin/feat/missing-features' into feat…
pixincreate Jul 31, 2026
3f0f0b2
chore: upgrade workflow actions to latest, add git to Docker image
pixincreate Aug 1, 2026
9329a92
fix: stabilize baseline fingerprints
pixincreate Aug 1, 2026
5f5fc98
fix: validate detector severity and names
pixincreate Aug 1, 2026
df0669a
fix: harden file and git history scanning
pixincreate Aug 1, 2026
1113feb
fix: harden generated git hooks
pixincreate Aug 1, 2026
3d6d955
style: group Rust imports
pixincreate Aug 1, 2026
d6b9ffb
fix: harden composite scan action
pixincreate Aug 1, 2026
99e9f2c
fix: align Docker and release publishing
pixincreate Aug 1, 2026
4ce00f9
Merge remote-tracking branch 'origin/feat/missing-features' into feat…
pixincreate Aug 1, 2026
eedadcb
fix: guard Unix-only hook test imports
pixincreate Aug 1, 2026
699f621
Merge branch 'feat/missing-features' into feat/github-action-docker
pixincreate Aug 1, 2026
ec06083
refactor: clarify pre-push remote policy shell
pixincreate Aug 2, 2026
15dc56a
refactor: simplify baseline hashing
pixincreate Aug 2, 2026
60d25b0
style: use descriptive scanner test errors
pixincreate Aug 2, 2026
c40a836
Merge branch 'feat/missing-features' into feat/github-action-docker
pixincreate Aug 2, 2026
26287b8
Merge master into feat/github-action-docker
pixincreate Aug 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.git/
.gitignore
.github/
target/
*.md
COMPARISON.md
**/*.rs.bk
.DS_Store
281 changes: 281 additions & 0 deletions .github/actions/keywatch-scan/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
name: 'KeyWatch Scan'
description: 'Scan files and directories for secrets with KeyWatch'
author: 'Pa1Nark'

inputs:
paths:
description: 'Paths to scan (space-separated, supports globs)'
required: false
default: '.'
args:
description: 'Additional CLI arguments to pass to key-watch'
required: false
default: ''
exit-mode:
description: 'Exit code behavior: strict, critical, or always'
required: false
default: 'strict'
output:
description: 'Path to write the JSON report file'
required: false
default: ''
verbose:
description: 'Deprecated: verbose scanner output is disabled to avoid logging matched secrets'
required: false
default: 'false'

outputs:
findings-count:
description: 'Number of findings detected'
value: ${{ steps.scan.outputs.findings_count }}
exit-code:
description: 'Exit code from the scan'
value: ${{ steps.scan.outputs.exit_code }}

runs:
using: 'composite'
steps:
- name: Install KeyWatch
shell: bash
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail

for required_tool in curl jq; do
if ! command -v "$required_tool" >/dev/null 2>&1; then
echo "ERROR: $required_tool is required on this runner" >&2
exit 1
fi
done

REPO="pixincreate/KeyWatch"
VERSION="${KEYWATCH_VERSION:-latest}"

curl_args=(-fsSL)
if [ -n "${GITHUB_TOKEN:-}" ]; then
curl_args+=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
fi

if [ "$VERSION" = "latest" ]; then
release_json=$(curl "${curl_args[@]}" \
"https://api.github.com/repos/$REPO/releases/latest")
VERSION=$(jq -er '.tag_name' <<<"$release_json")
elif [[ "$VERSION" != v* ]]; then
VERSION="v$VERSION"
fi

if [[ ! "$VERSION" =~ ^v[0-9A-Za-z._-]+$ ]]; then
echo "ERROR: invalid KeyWatch version/tag '$VERSION'" >&2
exit 1
fi

case "$RUNNER_OS" in
Linux) asset_os="linux"; exe_suffix="" ;;
macOS) asset_os="darwin"; exe_suffix="" ;;
Windows)
echo "ERROR: Windows runners are not supported by this composite action" >&2
exit 1
;;
*)
echo "ERROR: unsupported runner OS: $RUNNER_OS" >&2
exit 1
;;
esac

case "$RUNNER_ARCH" in
X64) asset_arch="x86_64" ;;
ARM64)
if [ "$asset_os" = "darwin" ]; then
asset_arch="aarch64"
else
echo "ERROR: unsupported runner architecture for $RUNNER_OS: $RUNNER_ARCH" >&2
exit 1
fi
;;
*)
echo "ERROR: unsupported runner architecture: $RUNNER_ARCH" >&2
exit 1
;;
esac

install_dir="$RUNNER_TEMP/keywatch/$VERSION"
bin_dir="$install_dir/bin"
config_path="$install_dir/detectors.toml"
binary_path="$bin_dir/key-watch$exe_suffix"
asset_name="keywatch-$asset_os-$asset_arch$exe_suffix"
binary_url="https://github.com/$REPO/releases/download/$VERSION/$asset_name"
config_url="https://raw.githubusercontent.com/$REPO/$VERSION/detectors.toml"

mkdir -p "$bin_dir"

echo "Downloading KeyWatch $VERSION for $asset_os-$asset_arch..."
curl -fsSL "$binary_url" -o "$binary_path"
chmod +x "$binary_path"

echo "Downloading KeyWatch detectors config from $VERSION..."
curl -fsSL "$config_url" -o "$config_path"

export PATH="$bin_dir:$PATH"
export KEYWATCH_CONFIG_PATH="$config_path"
echo "$bin_dir" >> "$GITHUB_PATH"
echo "KEYWATCH_CONFIG_PATH=$config_path" >> "$GITHUB_ENV"

echo "Installed KeyWatch $VERSION ($asset_name)"
key-watch --version

- name: Run KeyWatch Scan
id: scan
shell: bash
env:
INPUT_PATHS: ${{ inputs.paths }}
INPUT_ARGS: ${{ inputs.args }}
INPUT_EXIT_MODE: ${{ inputs.exit-mode }}
INPUT_OUTPUT: ${{ inputs.output }}
INPUT_VERBOSE: ${{ inputs.verbose }}
run: |
set -euo pipefail

for required_tool in jq; do
if ! command -v "$required_tool" >/dev/null 2>&1; then
echo "ERROR: $required_tool is required on this runner" >&2
exit 1
fi
done

if [ -z "${KEYWATCH_CONFIG_PATH:-}" ] || [ ! -f "$KEYWATCH_CONFIG_PATH" ]; then
echo "ERROR: KEYWATCH_CONFIG_PATH is not set to a readable detectors.toml" >&2
exit 1
fi

case "$INPUT_EXIT_MODE" in
always|critical|strict) ;;
*)
echo "ERROR: invalid exit-mode '$INPUT_EXIT_MODE' (expected: always, critical, strict)" >&2
exit 1
;;
esac

verbose_normalized=$(tr '[:upper:]' '[:lower:]' <<<"$INPUT_VERBOSE")
case "$verbose_normalized" in
true|1|yes|on)
echo "ERROR: verbose output is disabled in this action to avoid logging matched secrets" >&2
exit 1
;;
false|0|no|off|"") ;;
*)
echo "ERROR: invalid verbose value '$INPUT_VERBOSE' (expected: true or false)" >&2
exit 1
;;
esac

paths=()
if [ -n "$INPUT_PATHS" ]; then
read -r -a paths <<<"$INPUT_PATHS"
fi

expanded_paths=()
if ((${#paths[@]})); then
for path_token in "${paths[@]}"; do
case "$path_token" in
*'*'*|*'?'*|*'['*)
matches=()
while IFS= read -r match; do
matches+=("$match")
done < <(compgen -G "$path_token" || true)

if ((${#matches[@]})); then
expanded_paths+=("${matches[@]}")
else
expanded_paths+=("$path_token")
fi
;;
*)
expanded_paths+=("$path_token")
;;
esac
done
fi

extra_args=()
if [ -n "$INPUT_ARGS" ]; then
read -r -a extra_args <<<"$INPUT_ARGS"
fi

if ((${#extra_args[@]})); then
for arg in "${extra_args[@]}"; do
case "$arg" in
--verbose|--verbose=*|-v*|--format|--format=*|-f|-f*|--output|--output=*|-o|-o*|--exit-mode|--exit-mode=*)
echo "ERROR: '$arg' is managed by action inputs and cannot be passed through args" >&2
exit 1
;;
esac
done
fi

if [ -n "$INPUT_OUTPUT" ]; then
report_path="$INPUT_OUTPUT"
report_dir=$(dirname -- "$report_path")
if [ "$report_dir" != "." ]; then
mkdir -p "$report_dir"
fi
else
report_path="$RUNNER_TEMP/keywatch-report.json"
mkdir -p "$(dirname -- "$report_path")"
fi

keywatch_args=(scan)
keywatch_args+=(--exit-mode "$INPUT_EXIT_MODE")
if ((${#extra_args[@]})); then
keywatch_args+=("${extra_args[@]}")
fi
keywatch_args+=(--output "$report_path")
if ((${#expanded_paths[@]})); then
keywatch_args+=(-- "${expanded_paths[@]}")
fi

rm -f -- "$report_path"

echo "Running KeyWatch scan..."
set +e
key-watch "${keywatch_args[@]}"
scan_status=$?
set -e

findings_count="unknown"
report_status="missing"
if [ -s "$report_path" ]; then
if jq -e '.findings | type == "array"' "$report_path" >/dev/null 2>&1; then
findings_count=$(jq -r '.findings | length' "$report_path")
report_status="ok"
else
report_status="malformed"
fi
fi

action_status=$scan_status
if [ "$report_status" != "ok" ] && [ "$scan_status" -eq 0 ]; then
echo "ERROR: KeyWatch exited successfully but the JSON report is $report_status" >&2
action_status=2
fi

{
echo "exit_code=$scan_status"
echo "findings_count=$findings_count"
} >> "$GITHUB_OUTPUT"

{
echo "## KeyWatch Scan Results"
echo ""
echo "| Metric | Value |"
echo "|--------|-------|"
echo "| Findings | $findings_count |"
echo "| Exit Code | $scan_status |"
echo "| Report | $report_status |"
} >> "$GITHUB_STEP_SUMMARY"

exit "$action_status"

branding:
icon: 'shield'
color: 'blue'
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7

- name: Install Rust
uses: dtolnay/rust-toolchain@master
Expand All @@ -43,10 +43,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7

- name: Spell check
uses: crate-ci/typos@master
uses: crate-ci/typos@v1.48.0

test:
name: test-${{ matrix.runner }}
Expand All @@ -66,7 +66,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7

- name: Install mold linker
uses: rui314/setup-mold@v1
Expand All @@ -80,7 +80,7 @@ jobs:
toolchain: stable 2 weeks ago
components: clippy

- uses: Swatinem/rust-cache@v2.7.0
- uses: Swatinem/rust-cache@v2.9.1
with:
save-if: ${{ github.event_name == 'push' }}

Expand Down
54 changes: 54 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Publish Docker Image

on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
tag:
description: 'Image tag'
required: true
default: 'latest'

Comment thread
pixincreate marked this conversation as resolved.
permissions:
contents: read
packages: write

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-push:
name: Build and push Docker image
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v7

- name: Log in to GitHub Container Registry
uses: docker/login-action@v4.6.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v6.2.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,format=short
type=raw,value=${{ inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}

- name: Build and push Docker image
uses: docker/build-push-action@v7.3.0
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Loading