Skip to content

Enforce gh-aw bash blocklist across workflow, prompt, and skill surfaces#2090

Merged
pethers merged 4 commits intomainfrom
copilot/aw-detection-runs
Apr 28, 2026
Merged

Enforce gh-aw bash blocklist across workflow, prompt, and skill surfaces#2090
pethers merged 4 commits intomainfrom
copilot/aw-detection-runs

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 28, 2026

Recent threat-detection warnings on issue #1889 surfaced a request to harden agent-visible bash against the AWF sandbox blocklist documented in .github/prompts/01-bash-and-shell-safety.md and to test that only the three allowed eval replacements (arrays / case / explicit branches) are used.

Audit

Scanned every fenced shell block (including indented fences nested under list items) under .github/workflows/news-*.md, .github/prompts/*.md, and .github/skills/**/SKILL.md against every banned pattern row in the safety doc. One offender outside the safety doc itself: nested $(…$(…)…) inside $((…)) in the PAT-expiration example.

Changes

  • .github/skills/gh-aw-github-actions-integration/SKILL.md — pre-capture each $(date …) into its own variable; also handle the already-expired (negative-days) branch with an EXPIRED_DAYS=$((-DAYS_UNTIL_EXPIRATION)) helper so the workflow log line shows a positive day count.

    EXP_EPOCH=$(date -d "$EXPIRATION" +%s)
    NOW_EPOCH=$(date +%s)
    DAYS_UNTIL_EXPIRATION=$(( (EXP_EPOCH - NOW_EPOCH) / 86400 ))
  • tests/agentic-workflow-bash-safety.test.ts (new, 114 tests) — extracts every fenced shell block (column-0 or indented fences with matching open/close indentation) and matches against the full 7-row blocklist from 01-bash-and-shell-safety.md:

    • ${var@P/Q/E/A/a} parameter transformations
    • ${!var} indirect expansion
    • Nested $(…$(…)…) (incl. inside $((…)))
    • eval (any form: eval $var, eval -- "$var", plain eval cmd) / bash -c "$var" / source /dev/stdin <<<
    • Chained-builder command substitution c=$($b) (re-parsing a variable as a command)
    • echo / printf lines with two $(…) substitutions (AWF false-positive nested-cmd-subst)
    • Inline bash array literal (…) expanded with ${arr[@]} in the same command string

    Failures emit file:line + pattern + safe-rewrite. Two outer guards verify the safety doc still spells out all four base banned shapes and still names the three allowed eval replacements, so future drift fails loudly. The safety doc itself is the only allowlisted source of pattern strings.

    Skill scan is restricted to **/SKILL.md (the bodies actually mounted into the agent context) — README.md and other non-skill docs under .github/skills/ are out of scope.

Verified by poison-injection that all 7 patterns trigger with the expected per-pattern diagnostics, including indented fences.

Copilot AI linked an issue Apr 28, 2026 that may be closed by this pull request
@github-actions
Copy link
Copy Markdown
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

@github-actions github-actions Bot added the size-xs Extra small change (< 10 lines) label Apr 28, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🏷️ Automatic Labeling Summary

This PR has been automatically labeled based on the files changed and PR metadata.

Applied Labels: size-xs

Label Categories

  • 🗳️ Content: news, dashboard, visualization, intelligence
  • 💻 Technology: html-css, javascript, workflow, security
  • 📊 Data: cia-data, riksdag-data, data-pipeline, schema
  • 🌍 I18n: i18n, translation, rtl
  • 🔒 ISMS: isms, iso-27001, nist-csf, cis-controls
  • 🏗️ Infrastructure: ci-cd, deployment, performance, monitoring
  • 🔄 Quality: testing, accessibility, documentation, refactor
  • 🤖 AI: agent, skill, agentic-workflow

For more information, see .github/labeler.yml.

…ents across workflow/prompt/skill surface

- Add tests/agentic-workflow-bash-safety.test.ts (118 tests) scanning .github/workflows/news-*.md, .github/prompts/*.md, and .github/skills/**/*.md fenced code blocks for the 4 banned patterns documented in 01-bash-and-shell-safety.md (parameter transformations, indirect expansion, nested $(...) , eval / bash -c "$var" / source /dev/stdin)
- Verify the safety doc still names the 3 allowed eval replacements (arrays, case, explicit branches)
- Fix nested $(...$(...)...) staged-injection shape in gh-aw-github-actions-integration/SKILL.md PAT-expiration example by pre-capturing each substitution into EXP_EPOCH / NOW_EPOCH variables

Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/7d5e5371-c642-485d-b5a0-ff7494826890

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

@github-actions github-actions Bot added documentation Documentation updates testing Test coverage skill Skill configuration size-l Large change (250-1000 lines) labels Apr 28, 2026
- Document zsh fence in extractShellBlocks JSDoc (matches the actual fence list)
- Handle already-expired PAT case in skill example (negative DAYS_UNTIL_EXPIRATION) so the example does not silently mis-trigger when a token is already expired

Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/7d5e5371-c642-485d-b5a0-ff7494826890

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Copilot AI changed the title [WIP] Add tracking for detection runs in workflows Enforce gh-aw bash blocklist across workflow, prompt, and skill surfaces Apr 28, 2026
Copilot AI requested a review from pethers April 28, 2026 14:20
@github-actions
Copy link
Copy Markdown
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

@pethers pethers marked this pull request as ready for review April 28, 2026 14:22
Copilot AI review requested due to automatic review settings April 28, 2026 14:22
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens agent-visible shell snippets against the gh-aw AWF sandbox blocklist by updating a PAT-expiration example and adding a Vitest suite that scans workflow/prompt/skill markdown for banned bash patterns.

Changes:

  • Updates the PAT-expiration snippet to avoid nested command substitutions and adds handling for already-expired tokens.
  • Adds a new Vitest suite that extracts fenced shell blocks from agent-visible markdown and fails CI on banned patterns, with guard tests to detect drift in the safety doc wording.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
.github/skills/gh-aw-github-actions-integration/SKILL.md Refactors the PAT expiration calculation to avoid nested substitutions and adds an “already expired” branch.
tests/agentic-workflow-bash-safety.test.ts New test that scans fenced shell code blocks in workflows/prompts/skills for sandbox-blocked bash patterns and validates safety-doc consistency.

Comment on lines +80 to +84
// Match eval followed by a string- or variable-bearing argument.
// Eval on plain-literal flag-only invocations is also banned in our
// workflows -- there is no safe eval use in this repo.
regex:
/(^|[^A-Za-z_./])(eval\s+["'$\\]|bash\s+-c\s+["'][^"']*\$|source\s+\/dev\/stdin)/,
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The eval banned-pattern regex only matches eval when followed by a quote/$/backslash, so cases like eval $var or eval -- "$var" may not be attributed to the eval-on-variable pattern (they’d only be caught by the later “eval outside safety doc” guard). If the intent is “no eval anywhere”, consider widening this regex so the per-file violation report reliably points to the eval rule and safe rewrite.

Suggested change
// Match eval followed by a string- or variable-bearing argument.
// Eval on plain-literal flag-only invocations is also banned in our
// workflows -- there is no safe eval use in this repo.
regex:
/(^|[^A-Za-z_./])(eval\s+["'$\\]|bash\s+-c\s+["'][^"']*\$|source\s+\/dev\/stdin)/,
// Match any eval token, including cases such as `eval $var` and
// `eval -- "$var"`, plus the existing bash -c / source /dev/stdin forms.
// There is no safe eval use in this repo.
regex:
/(^|[^A-Za-z_./])(eval(?:\s|$)|bash\s+-c\s+["'][^"']*\$|source\s+\/dev\/stdin)/,

Copilot uses AI. Check for mistakes.
.readdirSync(PROMPTS_DIR)
.filter((f) => f.endsWith('.md') && f !== SAFETY_DOC)
.map((f) => path.join(PROMPTS_DIR, f)),
...listMarkdown(SKILLS_DIR),
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

listMarkdown(SKILLS_DIR) collects every .md under .github/skills/ (including README.md and any non-skill docs), but the header comment and PR description scope this to **/SKILL.md. Scanning extra markdown may create noisy failures unrelated to agent-visible skill bodies. Consider restricting the skills scan to **/SKILL.md files (or update the documented scope accordingly).

Suggested change
...listMarkdown(SKILLS_DIR),
...listMarkdown(SKILLS_DIR).filter((file) => path.basename(file) === 'SKILL.md'),

Copilot uses AI. Check for mistakes.
Comment on lines +746 to +749
echo "❌ Token already expired ($DAYS_UNTIL_EXPIRATION days ago) — rotate immediately"
gh issue create \
--title "🔐 GitHub PAT Expired" \
--body "Personal Access Token expired $((-DAYS_UNTIL_EXPIRATION)) days ago." \
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the expired-token branch, the log message prints a negative day count (e.g. “expired (-3 days ago)”), while the issue body correctly converts it to a positive number. Consider echoing the absolute day count as well to avoid confusing operators reading the workflow logs.

Suggested change
echo "❌ Token already expired ($DAYS_UNTIL_EXPIRATION days ago) — rotate immediately"
gh issue create \
--title "🔐 GitHub PAT Expired" \
--body "Personal Access Token expired $((-DAYS_UNTIL_EXPIRATION)) days ago." \
EXPIRED_DAYS=$((-DAYS_UNTIL_EXPIRATION))
echo "❌ Token already expired ($EXPIRED_DAYS days ago) — rotate immediately"
gh issue create \
--title "🔐 GitHub PAT Expired" \
--body "Personal Access Token expired $EXPIRED_DAYS days ago." \

Copilot uses AI. Check for mistakes.
Comment on lines +120 to +133
for (let i = 0; i < lines.length; i++) {
const line = lines[i] ?? '';
const fenceMatch = line.match(/^```([A-Za-z0-9_+-]*)\s*$/);
if (fenceMatch) {
if (!inBlock) {
inBlock = true;
lang = (fenceMatch[1] ?? '').toLowerCase();
bodyStart = i + 1;
bodyLines = [];
} else {
if (lang === '' || ['bash', 'sh', 'shell', 'console', 'zsh'].includes(lang)) {
blocks.push({ startLine: bodyStart + 1, body: bodyLines.join('\n') });
}
inBlock = false;
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extractShellBlocks only recognizes fences that start at column 0 (`^````), but many markdown files in this repo indent fenced blocks (e.g. under lists with leading spaces). Those indented blocks won’t be scanned, so banned patterns can slip in undetected. Consider allowing leading whitespace on both opening and closing fences (while still capturing the correct start line).

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +56
/**
* The blocklist -- kept in sync with the table in
* .github/prompts/01-bash-and-shell-safety.md (Banned expansion patterns).
*/
const BANNED_PATTERNS: readonly BannedPattern[] = [
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says the blocklist is “kept in sync” with .github/prompts/01-bash-and-shell-safety.md, but the safety doc currently lists additional sandbox-blocked shapes (e.g. chained builder assignments, the echo "…$(cmd)…" + another $(…) false-positive pattern, and the inline array+[@] case). As written, this test won’t prevent reintroducing those documented blocked patterns. Either expand BANNED_PATTERNS to cover all rows in the safety doc, or adjust the doc/test wording so they match.

Copilot uses AI. Check for mistakes.
@pethers
Copy link
Copy Markdown
Member

pethers commented Apr 28, 2026

@copilot apply changes based on the comments in this thread

…ten scope

- Expand BANNED_PATTERNS to all 7 rows of `01-bash-and-shell-safety.md`: add chained-builder $(\$var), echo/printf with two $(...), and inline array+[@] shapes
- Widen eval regex to catch `eval $var`, `eval -- "$var"`, plain `eval cmd` so per-file diagnostics name the eval-on-variable rule
- Allow indented fenced shell blocks (e.g. nested under list items) — opening/closing fence tolerate matching leading whitespace
- Restrict skill scan to `**/SKILL.md` only — README.md and other skill docs are not in the agent context
- Use EXPIRED_DAYS variable in expired-token branch so the log line shows a positive day count

Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/38522090-0594-47e1-bbad-f3276a3bdda8

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 28, 2026

@copilot apply changes based on the comments in this thread

Applied all five review suggestions in e15ced1:

  • Expanded BANNED_PATTERNS from 4 to 7 rows so every row in 01-bash-and-shell-safety.md §Banned expansion patterns is enforced (added chained-builder $(\$var), echo/printf with two $(...) on the same line, and inline (…) + [@] array shape).
  • Widened the eval-on-variable regex to eval(?:\s|$)|bash\s+-c\s+…|source\s+/dev/stdin so plain eval $var, eval -- "$var", and eval some-cmd are attributed to the rule with its safe-rewrite hint.
  • extractShellBlocks now allows indented fenced blocks (open/close fence with matching leading whitespace).
  • Restricted skill scan to **/SKILL.md only — README.md and other non-skill docs are no longer in the agent context (114/114 tests still pass).
  • PAT-expiration example uses EXPIRED_DAYS=$((-DAYS_UNTIL_EXPIRATION)) so the log line shows a positive day count.

Verified all 7 patterns by poison-injection, including the indented fence.

@github-actions
Copy link
Copy Markdown
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

@pethers pethers merged commit 56ba877 into main Apr 28, 2026
16 of 17 checks passed
@pethers pethers deleted the copilot/aw-detection-runs branch April 28, 2026 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Documentation updates size-l Large change (250-1000 lines) size-xs Extra small change (< 10 lines) skill Skill configuration testing Test coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[aw] Detection Runs

3 participants