Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5daefde
consumer-claude-code: mention-only triggers (propagated from template)
claude Jul 28, 2026
e8e9fd4
consumer-claude-code: propagate the corrected template
assisted-by-ai Jul 30, 2026
32cb7a2
CI: propagate codeql-actions + secrets-audit consumer workflows
claude Jul 31, 2026
e59019a
Merge pull request #26 from org-ai-assisted/ai
assisted-by-ai Aug 1, 2026
1fcf496
Merge pull request #23 from org-ai-assisted/ai-claude-review-mention-…
assisted-by-ai Aug 1, 2026
a2b1384
security-misc: R-010 strict for the module-disabled alert scripts
claude Aug 5, 2026
59b3e60
security-misc: R-010 strict for the PAM helpers, preserving exit codes
claude Aug 5, 2026
9d6c463
security-misc: R-010 strict for the remaining standalone scripts
claude Aug 5, 2026
1ec153c
fix shellcheck directives for an isolated CI checkout
claude Aug 7, 2026
a8f2606
Drain input in USB-controller and config-file lookups
claude Aug 10, 2026
ec78b99
Merge branch 'master' into ai
claude Aug 12, 2026
942ba8a
security-misc: permission-hardener parse filenames with spaces
claude Aug 12, 2026
8c991dd
security-misc: compile emerg-shutdown once at postinst, not every boot
claude Aug 12, 2026
8ba6ca1
security-misc: silence expected pam_exec journal noise via quiet_log
claude Aug 12, 2026
8aef705
security-misc: fm-shim frontend calls xdg-mime/gio by absolute path
claude Aug 12, 2026
9f12941
security-misc: document whitelisted SUID/SGID helpers, flag human-only
claude Aug 12, 2026
5b34c94
security-misc: dfuzzer commit pin, marker rewords, remount-secure cle…
claude Aug 12, 2026
c4bf540
security-misc: declare bash (>= 4.4) for inherit_errexit
claude Aug 12, 2026
a66fa50
security-misc: fix undo chown empty-arg + emerg fallback (ai-review)
claude Aug 12, 2026
c2fcb2d
security-misc: drop versioned bash dependency
claude Aug 12, 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
77 changes: 41 additions & 36 deletions .github/workflows/consumer-claude-code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,65 +14,70 @@

name: Claude Code Review

## Mention-driven only. claude-code-action picks its mode from the
## event: comment events with no 'prompt' input select tag mode,
## where the '@claude' phrase is the trigger and the model runs.
## Every other event (pull_request, workflow_dispatch, schedule)
## selects agent mode, whose trigger is a non-empty 'prompt' - which
## this workflow does not pass - so those events can only spin up a
## runner and exit without calling the model. Listing them here
## would put a green "Claude Code Review" check on every PR that
## never reviewed anything. See the reusable's header for the full
## rationale and for what enabling real auto-review would cost.
on:
pull_request:
branches: [master]
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
workflow_dispatch:

permissions:
contents: read

## issue_comment / pull_request_review_comment fire with
## github.ref = default branch (NOT the PR head ref). Plain
## ${{ github.ref }} would queue unrelated PRs' comment-triggered
## runs into the same group. The PR/issue-number fallback chain
## isolates per-PR.
## The two comment events differ: issue_comment fires with
## github.ref = the default branch, pull_request_review_comment with
## the PR merge ref (refs/pull/<number>/merge). Neither is the PR head
## ref, and plain ${{ github.ref }} would queue unrelated PRs'
## comment-triggered runs into the same group. The PR/issue-number
## chain below is what actually applies: one of those two number
## fields is populated for every event this workflow accepts, so
## github.ref is a fallback that is never reached here.
##
## Bot-vs-human split via the trailing actor-class suffix: when
## the Claude action posts a review comment, it does so as
## claude[bot], which fires this workflow again. Without the
## split, that bot-triggered run would land in the same
## concurrency group as the in-flight human @claude request, and
## 'cancel-in-progress: true' below would cancel the review
## mid-flight before any output reached the PR. The job-level
## 'if:' filter further down catches the bot run (claude[bot]'s
## own comment body does not contain '@claude'), but by then
## concurrency has already done the damage. GitHub's standard
## '[bot]' suffix on App accounts (claude[bot],
## github-advanced-security[bot], dependabot[bot], ...) lets a
## single endsWith() check isolate all bot-authored events into
## a separate group so they only cancel each other.
## Bot-vs-human split via the trailing actor-class suffix: when the
## Claude action posts a review comment, it does so as claude[bot],
## which fires this workflow again. Without the split, that
## bot-triggered run would land in the same concurrency group as the
## in-flight human @claude request, and 'cancel-in-progress: true'
## below would cancel the review mid-flight before any output
## reached the PR. The job-level 'if:' filter further down catches
## the bot run (claude[bot]'s own comment body does not contain
## '@claude'), but by then concurrency has already done the damage.
## GitHub's standard '[bot]' suffix on App accounts (claude[bot],
## github-advanced-security[bot], dependabot[bot], ...) lets a single
## endsWith() check isolate all bot-authored events into a separate
## group so they only cancel each other.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.issue.number || github.ref }}-${{ endsWith(github.actor, '[bot]') && 'bot' || 'human' }}
cancel-in-progress: true

jobs:
review:
## Pre-filter comment events for the '@claude' substring before
## the runner spins up. The reusable's job-level 'if:' enforces
## the same gate plus the allowed-users roster check, but doing
## the cheap substring test here too prevents a runner from
## starting (and stopping seconds later) for every unrelated
## comment on every issue/PR. pull_request and workflow_dispatch
## events pass through unconditionally.
if: |
github.event_name == 'pull_request' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issue_comment'
&& contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment'
&& contains(github.event.comment.body, '@claude'))
## Pre-filter for the '@claude' substring before the runner spins
## up. The reusable's job-level 'if:' enforces the same gate plus
## the allowed-users roster check, but doing the cheap substring
## test here too prevents a runner from starting (and stopping
## seconds later) for every unrelated comment on every issue/PR.
if: contains(github.event.comment.body, '@claude')
uses: org-ai-assisted/developer-meta-files/.github/workflows/reusable-claude-code-review.yml@master
## Explicit secret forwarding (NOT 'secrets: inherit'). With
## inherit, every caller-side secret would flow into the
## reusable; the explicit map caps the reusable's secrets
## context to exactly CLAUDE_CODE_OAUTH_TOKEN.
secrets:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
## 'contents: read' does NOT bound what Claude can do - the
## action swaps the runner token for an OIDC-minted claude[bot]
## App token carrying contents/issues/pull-requests write. The
## block is required for 'id-token: write'. See the reusable.
permissions:
contents: read
pull-requests: write
Expand Down
48 changes: 48 additions & 0 deletions .github/workflows/consumer-codeql-actions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

## AI-Assisted

## Managed by pkg_update_consumer_workflows. Byte-identical
## across consumers; this file is `cp`-ed from
## developer-meta-files/consumer-templates/.github/workflows/consumer-codeql-actions.yml.
## DO NOT hand-edit this file in the consumer repo - changes
## will be overwritten on the next propagation pass.
##
## Consumer wrapper for the 'actions' CodeQL language - workflow
## YAML security analysis (missing-permissions, script-injection,
## unversioned-immutable-actions, etc.).
##
## Cronless on purpose: byte-identical propagation forbids
## per-repo cron rewrites at propagation time. Push / PR /
## workflow_dispatch triggers cover the scan-on-change cases;
## rule-refresh re-scans can be kicked manually from the Actions
## tab.
##
## Reusable docs:
## https://github.com/org-ai-assisted/developer-meta-files/blob/master/.github/workflows/reusable-codeql.yml

name: CodeQL Actions

on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
actions:
uses: org-ai-assisted/developer-meta-files/.github/workflows/reusable-codeql.yml@master
with:
language: actions
permissions:
security-events: write
contents: read
41 changes: 41 additions & 0 deletions .github/workflows/consumer-secrets-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

## AI-Assisted

## Managed by pkg_update_consumer_workflows. Byte-identical
## across consumers; this file is `cp`-ed from
## developer-meta-files/consumer-templates/.github/workflows/consumer-secrets-audit.yml.
## DO NOT hand-edit this file in the consumer repo - changes
## will be overwritten on the next propagation pass.
##
## Manual-trigger entry point. Run from the Actions tab:
## 'Secrets surface audit' > 'Run workflow'. Use as a sanity
## check after changing any reusable's 'workflow_call.secrets'
## schema or any consumer's 'secrets:' map.
##
## 'secrets: inherit' here forwards the entire repo-level +
## org-level secret set into the audit reusable, which prints
## presence flags. The audit boolean is resolved at
## expression-evaluation time, so secret values themselves never
## land in env or step output.
##
## Reusable docs:
## https://github.com/org-ai-assisted/developer-meta-files/blob/master/.github/workflows/reusable-secrets-audit.yml

name: Secrets surface audit

on:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
audit:
uses: org-ai-assisted/developer-meta-files/.github/workflows/reusable-secrets-audit.yml@master
secrets: inherit
59 changes: 59 additions & 0 deletions .github/workflows/local-permission-hardener-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

## AI-Assisted

## Regression test for the permission-hardener config parser.
##
## Scope is intentionally narrow: it exercises load_state()'s
## right-anchored option parsing so a config filename containing a
## space is parsed as one entry instead of being split and silently
## dropped. See ci/tests/permission_hardener/test_whitespace_filename.sh.

name: Test permission-hardener

on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: Test permission-hardener
runs-on: ubuntu-latest
timeout-minutes: 10

## CI runs only where we enabled it; ANDed with the existing guard.
## Unset variable -> skipped, no runner, run stays green.
if: >-
vars.CI_ENABLED_ORG_AI_ASSISTED == 'true'
&& (github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request')

steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

## permission-hardener sources helper-scripts (log_run_die.sh,
## safe_echo.sh, strings.bsh) at startup; install-deps provides
## them under /usr/libexec/helper-scripts/.
- name: Install genmkfile + helper-scripts + safe-rm
uses: org-ai-assisted/developer-meta-files/.github/actions/install-deps@master
with:
apt-packages: 'safe-rm'

- name: Whitespace-in-filename parser regression test
## Root: the test writes a temp config under
## /etc/permission-hardener.d/ and runs 'print-policy'.
run: sudo -E ci/tests/permission_hardener/test_whitespace_filename.sh
21 changes: 18 additions & 3 deletions ci/dfuzzer-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,39 @@
##
## dfuzzer is NOT packaged in Ubuntu 24.04 noble (verified via
## packages.ubuntu.com - 'No such package'); hence the from-source
## build. Pinned to upstream tag v2.6 (latest release as of
## 2026-05-08). Bump when a new release lands.
## build. Pinned to upstream tag v2.6 AND its exact commit (verified
## below); latest release as of 2026-05-08. Bump both DFUZZER_TAG and
## DFUZZER_COMMIT together when a new release lands.

set -o errexit
set -o nounset
set -o pipefail
set -o errtrace
shopt -s inherit_errexit
shopt -s shift_verbose

## style-ok: no-tmp-hardcode (ephemeral CI build clone under /tmp/dfuzzer)

if [ "${CI:-}" != "true" ] && [ "${ALLOW_LOCAL:-}" != "true" ]; then
printf '%s\n' "${BASH_SOURCE[0]}: refusing to run outside CI. Set ALLOW_LOCAL=true to override." >&2
exit 1
fi

DFUZZER_TAG="${DFUZZER_TAG:-v2.6}"
## Pin the exact upstream commit. A tag is mutable; verifying the
## resolved commit hash detects a re-pointed tag (supply-chain guard).
## Keep in sync with DFUZZER_TAG on every version bump.
DFUZZER_COMMIT="${DFUZZER_COMMIT:-a955a80f7dd20fd7aeffad91da1c495aab5dbbd3}"

## TODO: Better to clone the whole repository, then check out a commit hash?
git clone --depth 1 --branch "${DFUZZER_TAG}" \
https://github.com/dbus-fuzzer/dfuzzer /tmp/dfuzzer

dfuzzer_actual_commit="$(git -C /tmp/dfuzzer rev-parse HEAD)"
if [ "${dfuzzer_actual_commit}" != "${DFUZZER_COMMIT}" ]; then
printf '%s\n' "${BASH_SOURCE[0]}: dfuzzer tag ${DFUZZER_TAG} resolved to ${dfuzzer_actual_commit}, expected ${DFUZZER_COMMIT} -- refusing (tag moved?)." >&2
exit 1
fi

meson setup --buildtype=release /tmp/dfuzzer/build /tmp/dfuzzer
ninja -C /tmp/dfuzzer/build -v
sudo install -m 0755 /tmp/dfuzzer/build/dfuzzer /usr/local/bin/dfuzzer
Expand Down
69 changes: 69 additions & 0 deletions ci/tests/permission_hardener/test_whitespace_filename.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/bin/bash
## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

## AI-Assisted

## Regression test: permission-hardener must parse a config filename that
## contains spaces.
##
## load_state() recovers the filename by reading the option fields from the
## right (a trailing whitelist keyword, or the mode/owner/group[/capability]
## tail anchored by the octal mode). A space in the filename must NOT split it
## into the wrong fields -- that drops the entry silently, leaving a SUID
## binary un-hardened.
##
## Drives the REAL script via 'print-policy' with a mode-form entry
## (<filename> <mode> <owner> <group>) whose filename contains a space, and
## asserts the recovered filename appears in the printed policy.
##
## Requires root: writes a temporary config under /etc/permission-hardener.d/
## and needs helper-scripts installed (sourced by permission-hardener).

set -o errexit
set -o nounset
set -o pipefail
set -o errtrace
shopt -s inherit_errexit
shopt -s shift_verbose

repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../../.." && pwd)"
ph_bin="${PERMISSION_HARDENER_BIN:-${repo_root}/usr/bin/permission-hardener#security-misc-shared}"

if [ ! -f "${ph_bin}" ]; then
printf '%s\n' "FAIL: permission-hardener not found at '${ph_bin}'." >&2
exit 1
fi

test_dir="$(mktemp -d -t ph-ws-test.XXXXXX)"
## The space in the directory name is the property under test.
spaced_file="${test_dir}/some space/binary"
mkdir -p -- "${test_dir}/some space"
touch -- "${spaced_file}"

config_dir="/etc/permission-hardener.d"
config_file="${config_dir}/zz-ai-whitespace-regression-test.conf"
mkdir -p -- "${config_dir}"

## invoked indirectly via 'trap ... EXIT'
# shellcheck disable=SC2317
cleanup() {
safe-rm -f -- "${config_file}"
safe-rm -rf -- "${test_dir}"
}
trap cleanup EXIT

## mode-form entry: <filename> <mode> <owner> <group>
printf '%s\n' "${spaced_file} 0744 root root" > "${config_file}"

policy_output="$( "${ph_bin}" print-policy )"

if printf '%s\n' "${policy_output}" | grep -qF -- "${spaced_file}"; then
printf '%s\n' "PASS: space-containing filename parsed and present in policy."
exit 0
fi

printf '%s\n' "FAIL: space-containing filename '${spaced_file}' missing from print-policy output." >&2
printf '%s\n' "----- print-policy output -----" >&2
printf '%s\n' "${policy_output}" >&2
exit 1
1 change: 1 addition & 0 deletions debian/security-misc-shared.install
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ usr/lib/systemd/user/usbguard-notifier.service.d/30_security-misc.conf#security-
usr/lib/udev/rules.d/95-emerg-shutdown.rules#security-misc-shared => /usr/lib/udev/rules.d/95-emerg-shutdown.rules
usr/libexec/security-misc/askpass#security-misc-shared => /usr/libexec/security-misc/askpass
usr/libexec/security-misc/block-unsafe-logins#security-misc-shared => /usr/libexec/security-misc/block-unsafe-logins
usr/libexec/security-misc/build-emerg-shutdown#security-misc-shared => /usr/libexec/security-misc/build-emerg-shutdown
usr/libexec/security-misc/build-fm-shim-backend#security-misc-shared => /usr/libexec/security-misc/build-fm-shim-backend
usr/libexec/security-misc/check-for-usb-controller#security-misc-shared => /usr/libexec/security-misc/check-for-usb-controller
usr/libexec/security-misc/compile-emerg-shutdown#security-misc-shared => /usr/libexec/security-misc/compile-emerg-shutdown
Expand Down
Loading