Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bff0e4b
fix(#2247): compare decoded text in shim drift detection
fullsend-ai-coder[bot] Jun 12, 2026
7dcf28c
Add QualityFlow output for GH-2247 [skip ci]
Jun 21, 2026
46421d7
Add STP output for GH-2247 [skip ci]
Jun 21, 2026
bae810c
Add QualityFlow STP review output for GH-2247 [skip ci]
Jun 21, 2026
13f4a46
Add QualityFlow output for GH-2247 [skip ci]
Jun 21, 2026
040cb88
Add QualityFlow output for GH-2247 [skip ci]
Jun 21, 2026
042e66a
Add QualityFlow STD review output for GH-2247 [skip ci]
Jun 21, 2026
d83e032
Add QualityFlow STD review for GH-2247 [skip ci]
Jun 21, 2026
9ccc0ed
Add test output for GH-2247 [skip ci]
Jun 21, 2026
32fce47
Add QualityFlow tests for GH-2247
guyoron1 Jun 21, 2026
97ed527
Add QualityFlow output for GH-77 [skip ci]
Jun 22, 2026
254d9af
Add STP output for GH-77 [skip ci]
Jun 22, 2026
b035d34
Add QualityFlow STP review for GH-77 [skip ci]
Jun 22, 2026
103585e
Add QualityFlow output for GH-77 [skip ci]
Jun 22, 2026
49a680e
Add QualityFlow output for GH-77 [skip ci]
Jun 22, 2026
10095e9
Add QualityFlow STD review for GH-77 [skip ci]
Jun 22, 2026
737030a
Add QualityFlow output for GH-77 [skip ci]
Jun 22, 2026
1469d16
Add QualityFlow tests for GH-77 [skip ci]
Jun 22, 2026
4a11fb9
Clean QualityFlow artifacts for GH-77
guyoron1 Jun 22, 2026
fad4647
chore: remove old qf-tests/ artifacts
guyoron1 Jun 22, 2026
fae9844
Add QualityFlow output for GH-77 [skip ci]
Jun 22, 2026
4c46925
Add STP output for GH-77 [skip ci]
Jun 22, 2026
e05c700
Add QualityFlow STP review for GH-77 [skip ci]
Jun 22, 2026
1607e80
Add QualityFlow output for GH-77 [skip ci]
Jun 22, 2026
cc5e08d
Add QualityFlow output for GH-77 [skip ci]
Jun 22, 2026
b061b80
Refine STD for GH-77: resolve 5 findings (2 CRITICAL, 3 MAJOR)
Jun 22, 2026
9559260
Add QualityFlow tests for GH-77 [skip ci]
Jun 22, 2026
0085a01
Clean QualityFlow artifacts for GH-77
guyoron1 Jun 22, 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
3 changes: 0 additions & 3 deletions CLAUDE.md

This file was deleted.

105 changes: 105 additions & 0 deletions internal/scaffold/fullsend-repo/scripts/reconcile-repos-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -743,3 +743,108 @@ if ! grep -q "::warning::test-repo: non-comment content above sentinel was rejec
fi

echo "PASS: non-comment YAML above sentinel rejected by content-injection guard"

# ===========================
# Test 5: identical content with different trailing newlines is not flagged as stale
# ===========================

# Regression test for issue #2247: the old managed_content_b64 comparison
# produced false-positive drift detection when the remote and expected
# content were logically identical but encoded with different trailing
# newlines (e.g. one trailing \n vs two from the GitHub content API).
# The fix compares decoded text instead of re-encoded base64.

rm -f "${GH_LOG}" "${TMPDIR}/blob-input-test-repo.json"

# Generate the expected content (template with sentinel) — the "truth".
IDENTICAL_MANAGED=$(cat "${CONFIG_DIR}/templates/shim-workflow-call.yaml")
# The remote has the same text but an extra trailing newline, producing
# different base64 from shim_content_b64. This simulates encoding
# differences that can arise from GitHub's content API.
IDENTICAL_REMOTE=$(printf '%s\n\n' "$IDENTICAL_MANAGED")
IDENTICAL_B64=$(printf '%s' "$IDENTICAL_REMOTE" | /usr/bin/base64 | tr -d '\r\n')

cat > "${MOCK_BIN}/gh" <<EOF5
#!/usr/bin/env bash
set -euo pipefail
printf 'gh' >> "${GH_LOG}"
for arg in "\$@"; do
printf ' %q' "\$arg" >> "${GH_LOG}"
done
printf '\n' >> "${GH_LOG}"

if [[ "\$1" == "pr" ]]; then
exit 0
fi

if [[ "\$1" != "api" ]]; then
exit 0
fi

jq_filter=""
has_input=false
shift
endpoint="\$1"; shift
while [[ \$# -gt 0 ]]; do
case "\$1" in
--jq) jq_filter="\$2"; shift 2 ;;
--input) has_input=true; shift 2 ;;
--method|--field) shift 2 ;;
--silent) shift ;;
*) shift ;;
esac
done

if [[ "\$has_input" == "true" && "\$endpoint" == *"/git/blobs" ]]; then
cat > "${TMPDIR}/blob-input-test-repo.json"
fi

json=""
rc=0
case "\$endpoint" in
repos/test-org/test-repo/actions/variables/*)
json='{"status":"404","message":"Not Found"}'
rc=1
;;
repos/test-org/test-repo/contents/.github/workflows/fullsend.yaml)
json='{"content":"${IDENTICAL_B64}","sha":"file-sha"}'
;;
repos/test-org/test-repo)
json='{"default_branch":"main","private":false}'
;;
*)
rc=0
;;
esac

if [[ -n "\$json" ]]; then
if [[ -n "\$jq_filter" ]]; then
printf '%s' "\$json" | jq -r "\$jq_filter"
else
printf '%s\n' "\$json"
fi
fi
exit "\$rc"
EOF5
chmod +x "${MOCK_BIN}/gh"

bash "${RECONCILE_SCRIPT}" "${CONFIG_DIR}" > "${TMPDIR}/stdout5.log" 2>&1 || true

if grep -q "shim is stale" "${TMPDIR}/stdout5.log"; then
echo "FAIL: identical content with different trailing newline was flagged as stale"
cat "${TMPDIR}/stdout5.log"
exit 1
fi

if ! grep -q "already enrolled (shim up to date)" "${TMPDIR}/stdout5.log"; then
echo "FAIL: identical content with different trailing newline was not recognized as current"
cat "${TMPDIR}/stdout5.log"
exit 1
fi

if [ -f "${TMPDIR}/blob-input-test-repo.json" ]; then
echo "FAIL: blob was created for identical content (false positive drift)"
exit 1
fi

echo "PASS: identical content with different trailing newlines not flagged as stale"
12 changes: 10 additions & 2 deletions internal/scaffold/fullsend-repo/scripts/reconcile-repos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,16 @@ if [ -n "$ENABLED_REPOS" ]; then
EXPECTED_B64=$(shim_content_b64)
# GitHub returns base64 with newlines; strip them for comparison.
REMOTE_B64=$(printf '%s' "$REMOTE_CONTENT" | tr -d '\r\n')
REMOTE_MANAGED=$(managed_content_b64 "$REMOTE_B64")
EXPECTED_MANAGED=$(managed_content_b64 "$EXPECTED_B64")
# Compare decoded text instead of re-encoded base64 to avoid
# false-positive drift detection from encoding differences
# (trailing newlines, line wrapping in command substitution).
EXPECTED_DECODED=$(printf '%s' "$EXPECTED_B64" | base64 -d | tr -d '\r')
REMOTE_DECODED=$(printf '%s' "$REMOTE_B64" | base64 -d | tr -d '\r')
EXPECTED_MANAGED=$(printf '%s\n' "$EXPECTED_DECODED" | extract_managed_content)
REMOTE_MANAGED=$(printf '%s\n' "$REMOTE_DECODED" | extract_managed_content)
# When no sentinel is found (pre-sentinel shim), compare full decoded content.
[ -z "$EXPECTED_MANAGED" ] && EXPECTED_MANAGED="$EXPECTED_DECODED"
[ -z "$REMOTE_MANAGED" ] && REMOTE_MANAGED="$REMOTE_DECODED"
if [ "$REMOTE_MANAGED" = "$EXPECTED_MANAGED" ]; then
echo "✓ $REPO already enrolled (shim up to date)"
SKIPPED=$((SKIPPED + 1))
Expand Down
115 changes: 115 additions & 0 deletions internal/scaffold/qf_content_injection_guard_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package scaffold

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

/*
Content-Injection Guard Tests — YAML Injection Prevention

STP Reference: outputs/stp/GH-77/GH-77_test_plan.md
Jira: GH-77

These tests verify that the content-injection guard in shim_with_header_b64()
correctly rejects non-comment YAML above the sentinel line while preserving
legitimate comment-only headers (e.g., license headers).
*/

func TestContentInjectionGuard(t *testing.T) {
t.Run("[test_id:TS-GH77-014] should reject non-comment YAML above sentinel", func(t *testing.T) {
h := newReconcileHarness(t)

// Remote shim has non-comment YAML key injected above sentinel.
remoteContent := "name: injected-workflow\n# --- fullsend managed below - do not edit ---\nstale shim template\n"
remoteB64 := b64encode(remoteContent)

h.writeGHMock(ghMockOpts{
prBlock: `
case "$2" in
list)
for arg in "$@"; do
if [[ "$arg" == "fullsend/onboard" ]]; then
echo "https://github.com/test-org/test-repo/pull/99"
fi
done
exit 0 ;;
create) echo "https://github.com/test-org/test-repo/pull/99"; exit 0 ;;
close) exit 0 ;;
esac
exit 0`,
apiCases: fmt.Sprintf(`repos/test-org/test-repo/contents/*)
json='{"content":"%s","sha":"file-sha"}'
;;
`, remoteB64),
})

output, _ := h.run()

// Verify blob was created (content was stale).
assert.True(t, h.blobExists("test-repo"),
"blob should be created for injection-guarded shim update")

blobDecoded := h.blobContent("test-repo")

// Verify injected YAML was stripped.
assert.NotContains(t, blobDecoded, "injected-workflow",
"injected YAML key should be stripped from blob content")

// Verify warning was emitted.
assert.Contains(t, output, "non-comment content above sentinel was rejected",
"warning log should be emitted for rejected content")

// Verify blob still contains valid template.
assert.Contains(t, blobDecoded, "# --- fullsend managed below - do not edit ---",
"sentinel line should be present in the updated blob")
assert.Contains(t, blobDecoded, "fresh shim template",
"fresh template content should be present after guard")
})

t.Run("[test_id:TS-GH77-015] should preserve comment-only header during update", func(t *testing.T) {
h := newReconcileHarness(t)

// Remote shim has comment-only header (license) + sentinel + stale managed content.
remoteContent := "# Copyright 2026 Conforma\n# SPDX-License-Identifier: Apache-2.0\n# --- fullsend managed below - do not edit ---\nstale shim template\n"
remoteB64 := b64encode(remoteContent)

h.writeGHMock(ghMockOpts{
prBlock: `
case "$2" in
list) exit 0 ;;
create) echo "https://github.com/test-org/test-repo/pull/99"; exit 0 ;;
close) exit 0 ;;
esac
exit 0`,
apiCases: fmt.Sprintf(`repos/test-org/test-repo/contents/*)
json='{"content":"%s","sha":"file-sha"}'
;;
`, remoteB64),
})

output, _ := h.run()

// Verify stale detection.
assert.Contains(t, output, "shim is stale",
"stale managed content should be detected")

// Verify blob was created with preserved header.
assert.True(t, h.blobExists("test-repo"),
"blob should be created for stale shim update")
blobDecoded := h.blobContent("test-repo")

assert.Contains(t, blobDecoded, "# Copyright 2026 Conforma",
"comment header should be preserved in updated blob")
assert.Contains(t, blobDecoded, "# SPDX-License-Identifier: Apache-2.0",
"SPDX identifier should be preserved")
assert.Contains(t, blobDecoded, "# --- fullsend managed below - do not edit ---",
"sentinel line should be present")
assert.Contains(t, blobDecoded, "fresh shim template",
"managed section should be updated with fresh template")
assert.NotContains(t, blobDecoded, "stale shim template",
"old managed content should be replaced")
})
}
69 changes: 69 additions & 0 deletions internal/scaffold/qf_crlf_normalization_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package scaffold

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

/*
CR/LF Normalization Tests — Cross-Platform Drift Prevention

STP Reference: outputs/stp/GH-77/GH-77_test_plan.md
Jira: GH-77

These tests verify that the tr -d '\r' normalization in reconcile-repos.sh
correctly handles Windows-style line endings, preventing false-positive
drift detection from CR/LF differences.
*/

func TestCRLFNormalization(t *testing.T) {
t.Run("[test_id:TS-GH77-012] should normalize CRLF content before comparison", func(t *testing.T) {
h := newReconcileHarness(t)

// Remote content has CRLF line endings throughout.
remoteContent := "# --- fullsend managed below - do not edit ---\r\nfresh shim template\r\n"
remoteB64 := b64encode(remoteContent)

h.writeGHMock(ghMockOpts{
prBlock: `exit 0`,
apiCases: fmt.Sprintf(`repos/test-org/test-repo/contents/*)
json='{"content":"%s","sha":"file-sha"}'
;;
`, remoteB64),
})

output, exitCode := h.run()

assert.Equal(t, 0, exitCode, "script should exit successfully")
assert.Contains(t, output, "already enrolled (shim up to date)",
"CRLF content should be recognized as up-to-date after normalization")
assert.NotContains(t, output, "shim is stale",
"CRLF differences should not cause false drift")
})

t.Run("[test_id:TS-GH77-013] should handle mixed line endings correctly", func(t *testing.T) {
h := newReconcileHarness(t)

// Remote content has mixed line endings: first line CRLF, second line LF.
remoteContent := "# --- fullsend managed below - do not edit ---\r\nfresh shim template\n"
remoteB64 := b64encode(remoteContent)

h.writeGHMock(ghMockOpts{
prBlock: `exit 0`,
apiCases: fmt.Sprintf(`repos/test-org/test-repo/contents/*)
json='{"content":"%s","sha":"file-sha"}'
;;
`, remoteB64),
})

output, exitCode := h.run()

assert.Equal(t, 0, exitCode, "script should exit successfully")
assert.NotContains(t, output, "shim is stale",
"mixed line endings should not cause false drift")
assert.Contains(t, output, "already enrolled (shim up to date)",
"mixed-ending content should be recognized as up-to-date")
})
}
Loading