Skip to content

ci: run sbom/behavior/redteam e2e suite on develop and main PRs - #237

Draft
KanishkThamman wants to merge 6 commits into
mainfrom
bug/wire-e2e-pipeline-into-ci
Draft

ci: run sbom/behavior/redteam e2e suite on develop and main PRs#237
KanishkThamman wants to merge 6 commits into
mainfrom
bug/wire-e2e-pipeline-into-ci

Conversation

@KanishkThamman

Copy link
Copy Markdown
Collaborator

Closes #236

What

Wires NuGuard's own sbom/behavior/redteam pipeline into CI so regressions are
caught automatically instead of relying on someone remembering to run
tests/apps/prepublish-sanity.sh by hand before a release.

  • New .github/workflows/nuguard-pipeline-e2e.yml:
    • develop-e2e — runs the suite against 1 target app (openai-cs-agents-demo)
      on PRs into develop.
    • main-e2e — runs the suite against all 3 target apps
      (openai-cs, gemini-auto, pinnacle-bank) as a parallel matrix on PRs
      into main; all 3 must pass.
    • Both use redteam.profile: ci and NuGuard's own LLM via
      AZURE_OPENAI_KEY / AZURE_API_BASE repo secrets.
    • Failures upload the behavior/redteam report artifacts, and every failure
      path in the script now emits a ::error:: annotation naming the app,
      stage, and reason.
  • tests/apps/prepublish-sanity.sh:
    • Accepts an optional app-name filter argument so both CI jobs reuse the
      same script instead of duplicating logic.
    • --profile ci made explicit on the redteam invocation.
    • JSON quality-gate failures (previously a silent SystemExit under
      set -e) now surface via ::error:: annotations too.

PR Type

  • Bug fix
  • Feature

Test plan

  • bash -n syntax check on the modified script
  • actionlint + shellcheck against the new workflow and script — clean
  • Isolated logic tests for the app-name filter, unmatched-name error path,
    and gate-check annotation wrapping (success + failure cases)
  • This PR itself is the live test — needs AZURE_OPENAI_KEY /
    AZURE_API_BASE set as repo secrets for main-e2e to actually pass
    rather than fail on missing credentials.

🤖 Generated with Claude Code

KanishkThamman and others added 2 commits August 10, 2026 03:47
Wires the existing prepublish-sanity.sh script (previously only run
manually) into GitHub Actions: develop-branch PRs get a fast one-app
check (openai-cs-agents-demo), main-branch PRs get the full 3-app
matrix, both using the ci redteam profile and NuGuard's own Azure
OpenAI resource via repo secrets.

Extends prepublish-sanity.sh with an optional app-name filter arg so
both CI jobs reuse the same script, and makes --profile ci explicit
on the redteam invocation rather than relying solely on each app's
committed config.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
run_with_allowed_rc already labels its exit failures with the app
name, but that message only showed up buried in step logs. The two
Python JSON-gate checks (behavior/redteam quality gates) were worse:
a SystemExit(reason) would silently abort the script via set -e with
no app-labeled message at all.

Every failure path now emits a ::error:: annotation naming the app,
stage, and reason, so it shows up in the job's Annotations panel and
PR checks summary without opening raw logs. Combined with main-e2e's
per-app matrix job names, it's unambiguous which app broke and why.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@KanishkThamman
KanishkThamman force-pushed the bug/wire-e2e-pipeline-into-ci branch from 3a11fd4 to c5e1ce9 Compare August 10, 2026 03:47
KanishkThamman and others added 2 commits August 10, 2026 04:01
main-e2e's gemini-auto and pinnacle-bank legs previously pointed at
source: https://github.com/NuGuardAI/{Gemini-Car-Assistant,Fintech-App}
via a github.token: ${GITHUB_TOKEN} block. Both repos are private and
the default Actions token is scoped only to this repo, so cloning them
would fail without a separately-provisioned cross-repo PAT.

Vendoring both apps' source directly into their nuguard.prepublish.yaml
directories (source: .) removes that dependency entirely, matching how
openai-cs-agents-demo already works. The pinnacle-bank-app export's own
stale nuguard-test-results/ (an old, unrelated scan-output dump, not
app source) was left out.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The workflow already uploaded behavior/redteam JSON+markdown reports
as artifacts, but that requires downloading a zip to see anything.
This org's default GITHUB_TOKEN has a read-only permission ceiling
(see enforce-base-branch.yml/require-linked-issue.yml), so posting
results as a PR comment isn't an option without a separate
write-scoped bot token.

Instead, write_job_summary() appends each app's behavior + redteam
markdown report to $GITHUB_STEP_SUMMARY — visible directly on the
run page. Called on the success path and every failure path (sbom
hard-fail, disallowed exit code, and both JSON quality-gate checks)
so a failed run's partial report is visible too, not just its
::error:: annotation. No-op outside Actions, where the env var is
unset.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment on lines +217 to +240
app.post('/api/auth/logout', async (req, res) => {
const authHeader = req.headers['authorization'];
if (!authHeader?.startsWith('Bearer ')) {
res.status(400).json({ error: 'Authorization header with Bearer token required' });
return;
}
try {
const payload = jwt.verify(authHeader.slice(7), JWT_SECRET as string) as jwt.JwtPayload;
if (payload.jti && payload.exp) {
revokeToken(payload.jti, payload.exp);
}
// Best-effort: revoke the Google OAuth token so it cannot be reused
if (payload.googleAccessToken) {
fetch(
`https://oauth2.googleapis.com/revoke?token=${encodeURIComponent(payload.googleAccessToken)}`,
{ method: 'POST' },
).catch(() => { /* ignore — token may already be expired */ });
}
res.json({ message: 'Logged out successfully' });
} catch {
// Even if the token is already invalid/expired, treat as success
res.json({ message: 'Logged out successfully' });
}
});
Comment on lines +247 to +267
app.post('/api/auth/verify', (req, res) => {
const authHeader = req.headers['authorization'];
if (!authHeader?.startsWith('Bearer ')) {
res.status(401).json({ error: 'Authorization header with Bearer token required' });
return;
}
try {
const payload = jwt.verify(authHeader.slice(7), JWT_SECRET as string) as jwt.JwtPayload;
if (payload.jti && isRevoked(payload.jti)) {
res.status(401).json({ valid: false, error: 'Token has been revoked' });
return;
}
res.json({
valid: true,
user: { name: payload.name, email: payload.sub, picture: payload.picture },
expiresAt: payload.exp ? new Date(payload.exp * 1000).toISOString() : null,
});
} catch {
res.status(401).json({ valid: false, error: 'Invalid or expired session token' });
}
});
});

// ── ADK Agent Chat Endpoint ──────────────────────────────────────────────────
app.post('/api/agent/chat', injectAuthFromJWT, async (req, res) => {
Comment on lines +295 to +297
app.get('*', (_req, res) => {
res.sendFile(join(__dirname, 'dist', 'index.html'));
});
KanishkThamman and others added 2 commits August 10, 2026 04:12
All 3 main-e2e legs failed with exit 126 (Permission denied) calling
./tests/apps/prepublish-sanity.sh directly. The script has never been
executable in this repo, even before this branch's changes (100644 at
the merge-base with main) -- it's presumably always been run locally
as `bash tests/apps/prepublish-sanity.sh`. Match that invocation in
the workflow instead of changing the file's mode.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Vendoring Fintech-App's source into tests/apps/pinnacle-bank-app/
pulled in its own Playwright-based e2e test (e2e_tests/test_fintech_e2e.py),
which pytest's default discovery swept into NuGuard's own test run,
failing collection with ModuleNotFoundError: No module named 'playwright'
(not a NuGuard dependency).

Same fix pattern already used for the other vendored fixture apps
(contact-center-ai-samples, blissful-store, claude-cookbooks,
ai-agents-google-adk) — add an --ignore entry in pyproject.toml's
pytest addopts rather than touching individual CI workflow commands.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

Review notes — logic/security look sound (correct exit-126 fix via bash tests/apps/prepublish-sanity.sh, correct secret handling via secrets.*, e2e-suite exclusion correctly scoped to only the vendored app's own tests). Two things to clean up before undrafting:

  1. tests/apps/pinnacle-bank-app/nuguard-test-results/ is untracked, ~3.2MB of local scan output, and not covered by any .gitignore pattern (confirmed via git check-ignore) — should be gitignored/removed before merge.
  2. Confirm AZURE_OPENAI_KEY / AZURE_API_BASE repo secrets are actually provisioned, since this PR is itself the live test of the workflow.

Not approving yet — holding until the above are addressed.

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.

[Bug]: NuGuard's own sbom/behavior/redteam pipeline is never run in CI

2 participants