Skip to content

fix: security hardening for .claude/skills/release/skill.sh#108

Merged
muzahidul-opti merged 2 commits intomasterfrom
copilot/fix-security-issues-skill-sh
May 4, 2026
Merged

fix: security hardening for .claude/skills/release/skill.sh#108
muzahidul-opti merged 2 commits intomasterfrom
copilot/fix-security-issues-skill-sh

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 4, 2026

Eight shell scripting security and correctness bugs in the release skill script, ranging from sed delimiter injection to silent CI failures.

Summary

  • sed delimiter injection — switch /-delimited regex to \|...\| alternate delimiter; a version containing / would break the expression even after escaping
  • \n not interpolated in fallback release notes — replace double-quoted "...\n..." with printf so newlines are actual newlines, not literal \n
  • History expansion — add set +H at top; ! characters in CHANGELOG content could trigger bash history expansion in interactive shells
  • trap variable scoping bugtrap "rm -f '$DRY_RUN_LOG'" with single quotes means $DRY_RUN_LOG is never expanded; fix to trap "rm -f \"${DRY_RUN_LOG}\"" to expand at registration time
  • Silent CI failure on dry-run prompt — add [[ ! -t 0 ]] guard before read; non-interactive stdin (e.g. /dev/null in CI) would silently produce empty $REPLY with no actionable error
  • Unquoted $REPLY in [[ ]] — quote the variable per bash best practices
  • Pipeline exit code masking — replace if ! ... | tee with explicit ${PIPESTATUS[0]} capture so flutter failures are reliably detected regardless of tee exit status
  • gh auth status scope gap — add inline comment noting it only verifies authentication, not that the token holds the repo scope required to create releases

Test plan

Syntax validated with bash -n. Logic changes verified by inspection against each issue description.

Issues

Original prompt

Security Fixes for .claude/skills/release/skill.sh

Please fix the following security and correctness issues in .claude/skills/release/skill.sh. The current file content is:

#!/bin/bash
set -euo pipefail

# Optimizely Flutter SDK - Release (Step 2)
# Publishes to pub.dev and creates GitHub release

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

error() { echo -e "${RED}✗ Error: $1${NC}" >&2; exit 1; }
success() { echo -e "${GREEN}$1${NC}"; }
info() { echo -e "${YELLOW}$1${NC}"; }

VERSION="${1:-}"
PRERELEASE=""
[[ "${2:-}" == "--prerelease" ]] && PRERELEASE="--prerelease"

[[ -z "$VERSION" ]] && error "Usage: /release <version> [--prerelease]"
[[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9]+)?$ ]] && error "Invalid version: $VERSION"

# Pre-flight checks
info "Running pre-flight checks..."
[[ "$(git branch --show-current)" != "master" ]] && error "Must be on master branch"
git diff-index --quiet HEAD -- || error "Working tree has uncommitted changes"

PUBSPEC_VERSION=$(grep '^version:' pubspec.yaml | awk '{print $2}')
[[ "$PUBSPEC_VERSION" != "$VERSION" ]] && error "Version mismatch! pubspec.yaml: $PUBSPEC_VERSION, requested: $VERSION"

# Verify authentication
gh auth status &>/dev/null || error "GitHub CLI not authenticated. Run: gh auth login"

success "Pre-flight checks passed"

# Create temporary file for dry-run output (prevents symlink attacks)
DRY_RUN_LOG=$(mktemp)
trap "rm -f '$DRY_RUN_LOG'" EXIT

# Dry run
info "Running pub publish dry-run..."
if ! flutter packages pub publish --dry-run 2>&1 | tee "$DRY_RUN_LOG"; then
    echo ""
    read -p "Dry-run found warnings. Continue? (y/N) " -n 1 -r
    echo
    [[ ! $REPLY =~ ^[Yy]$ ]] && error "Aborted by user"
fi

# Publish
info "Publishing to pub.dev..."
flutter packages pub publish || error "Publishing failed"
success "Published to pub.dev!"

# Extract CHANGELOG
# Escape VERSION for use in sed regex (prevent regex metacharacter interpretation)
ESCAPED_VERSION=$(printf '%s\n' "$VERSION" | sed 's/[.[\*^$()+?{|]/\\&/g')
CHANGELOG_CONTENT=$(sed -n "/^## ${ESCAPED_VERSION}\$/,/^## [0-9]/p" CHANGELOG.md | sed '1d;$d')
[[ -z "$CHANGELOG_CONTENT" ]] && CHANGELOG_CONTENT="Release $VERSION\n\nSee CHANGELOG.md for details."

RELEASE_NOTES="## $VERSION

$CHANGELOG_CONTENT"

# Create GitHub release
info "Creating GitHub draft release..."
if [[ -n "$PRERELEASE" ]]; then
    GH_RELEASE_URL=$(gh release create "v${VERSION}" \
        --title "Release v${VERSION}" \
        --notes "$RELEASE_NOTES" \
        --target master \
        --draft \
        "$PRERELEASE")
else
    GH_RELEASE_URL=$(gh release create "v${VERSION}" \
        --title "Release v${VERSION}" \
        --notes "$RELEASE_NOTES" \
        --target master \
        --draft)
fi

success "GitHub draft release created!"

echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo -e "${GREEN}✓ Release complete!${NC}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Version: $VERSION"
echo "Pub.dev: https://pub.dev/packages/optimizely_flutter_sdk/versions/$VERSION"
echo "GitHub Release: $GH_RELEASE_URL"
echo ""
echo "Next Steps:"
echo "1. 🔍 Verify package on pub.dev (may take 1-10 minutes to appear)"
echo "2. ✏️  Review the draft release at: $GH_RELEASE_URL"
echo "3. ✅ Make the draft release public once verified"
echo ""

Issues to fix (in priority order):

🔴 Critical

1. sed delimiter injection via $ESCAPED_VERSION
The escaped version is interpolated directly into a /-delimited sed expression. A version containing / (even after escaping) breaks the delimiter. Fix by switching to \|...| alternate sed delimiter syntax:

# Before:
CHANGELOG_CONTENT=$(sed -n "/^## ${ESCAPED_VERSION}\$/,/^## [0-9]/p" CHANGELOG.md | sed '1d;$d')

# After:
CHANGELOG_CONTENT=$(sed -n "\|^## ${ESCAPED_VERSION}\$|,\|^## [0-9]|p" CHANGELOG.md | sed '1d;$d')

2. \n not interpreted in fallback release notes string
In bash, \n inside double-quoted strings is a literal backslash-n. Fix with $'...' ANSI-C quoting:

# Before:
[[ -z "$CHANGELOG_CONTENT" ]] && CHANGELOG_CONTENT="Release $VERSION\n\nSee CHANGELOG.md for details."

# After:
[[ -z "$CHANGELOG_CONTENT" ]] && CHANGELOG_CONTENT=$"Release ${VERSION}\n\nSee CHANGELOG.md for details."
# Use printf instead for portability:
[[ -z "$CHANGELOG_CONTENT" ]] && CHANGELOG_CONTENT=$(printf 'Release %s\n\nSee CHANGELOG.md for details.' "$VERSION")

🟠 High

3. Disable history expansion to prevent ! in CHANGELOG content from being expanded
Add set +H near the top of the script (after set -euo pipefail) to disable bash history expansion, which can be triggered by ! characters in $CHANGELOG_CONTENT in interactive shells.

4. Version validated after first use — move validation earlier
The version format regex check is on line 21, but VERSION has already been referenced on line 18 ([[ "${2:-}" == "--prerelease" ]] is fine...

This pull request was created from Copilot chat.

Copilot AI changed the title [WIP] Fix security and correctness issues in skill.sh fix: security hardening for .claude/skills/release/skill.sh May 4, 2026
Copilot AI requested a review from muzahidul-opti May 4, 2026 14:13
@muzahidul-opti muzahidul-opti marked this pull request as ready for review May 4, 2026 14:17
@muzahidul-opti muzahidul-opti merged commit 03cea04 into master May 4, 2026
6 of 7 checks passed
@muzahidul-opti muzahidul-opti deleted the copilot/fix-security-issues-skill-sh branch May 4, 2026 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants