diff --git a/internal/cli/run.go b/internal/cli/run.go index afa6a2c86..464e2e0b0 100644 --- a/internal/cli/run.go +++ b/internal/cli/run.go @@ -25,6 +25,7 @@ func newRunCmd() *cobra.Command { var targetRepo string var fullsendBinary string var envFiles []string + var noPostScript bool cmd := &cobra.Command{ Use: "run ", @@ -34,7 +35,7 @@ func newRunCmd() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { agentName := args[0] printer := ui.New(os.Stdout) - return runAgent(agentName, fullsendDir, outputBase, targetRepo, fullsendBinary, envFiles, printer) + return runAgent(agentName, fullsendDir, outputBase, targetRepo, fullsendBinary, envFiles, noPostScript, printer) }, } @@ -43,13 +44,14 @@ func newRunCmd() *cobra.Command { cmd.Flags().StringVar(&targetRepo, "target-repo", "", "path to the target repository") cmd.Flags().StringVar(&fullsendBinary, "fullsend-binary", "", "path to a Linux fullsend binary to copy into the sandbox (default: current executable)") cmd.Flags().StringArrayVar(&envFiles, "env-file", nil, "load environment variables from a dotenv file (repeatable)") + cmd.Flags().BoolVar(&noPostScript, "no-post-script", false, "skip post-script execution (agent still runs full inference)") _ = cmd.MarkFlagRequired("fullsend-dir") _ = cmd.MarkFlagRequired("target-repo") return cmd } -func runAgent(agentName, fullsendDir, outputBase, targetRepo, fullsendBinary string, envFiles []string, printer *ui.Printer) (runErr error) { +func runAgent(agentName, fullsendDir, outputBase, targetRepo, fullsendBinary string, envFiles []string, noPostScript bool, printer *ui.Printer) (runErr error) { printer.Banner() printer.Blank() printer.Header("Running agent: " + agentName) @@ -143,7 +145,11 @@ func runAgent(agentName, fullsendDir, outputBase, targetRepo, fullsendBinary str printer.KeyValue("Pre-script", h.PreScript) } if h.PostScript != "" { - printer.KeyValue("Post-script", h.PostScript) + if noPostScript { + printer.KeyValue("Post-script", h.PostScript+" (SKIPPED: --no-post-script)") + } else { + printer.KeyValue("Post-script", h.PostScript) + } } if h.TimeoutMinutes > 0 { printer.KeyValue("Timeout", fmt.Sprintf("%d minutes", h.TimeoutMinutes)) @@ -230,6 +236,10 @@ func runAgent(agentName, fullsendDir, outputBase, targetRepo, fullsendBinary str // any output checks it needs. if h.PostScript != "" { defer func() { + if noPostScript { + printer.StepWarn(fmt.Sprintf("Skipping post-script %s: --no-post-script", h.PostScript)) + return + } if h.ValidationLoop != nil && !validationPassed { printer.StepWarn("Skipping post-script: validation did not pass") return diff --git a/internal/cli/run_test.go b/internal/cli/run_test.go index bcfd9870f..27147bd2d 100644 --- a/internal/cli/run_test.go +++ b/internal/cli/run_test.go @@ -42,6 +42,13 @@ func TestRunCommand_RegisteredOnRoot(t *testing.T) { assert.True(t, found, "run command should be registered on root") } +func TestRunCommand_HasNoPostScriptFlag(t *testing.T) { + cmd := newRunCmd() + flag := cmd.Flags().Lookup("no-post-script") + require.NotNil(t, flag) + assert.Equal(t, "false", flag.DefValue) +} + func TestRunCommand_HasOutputDirFlag(t *testing.T) { cmd := newRunCmd() flag := cmd.Flags().Lookup("output-dir") diff --git a/internal/scaffold/fullsend-repo/.github/actions/fullsend/action.yml b/internal/scaffold/fullsend-repo/.github/actions/fullsend/action.yml index 8821fd4e3..b7fc9484c 100644 --- a/internal/scaffold/fullsend-repo/.github/actions/fullsend/action.yml +++ b/internal/scaffold/fullsend-repo/.github/actions/fullsend/action.yml @@ -113,6 +113,10 @@ runs: run: | set -euo pipefail mkdir -p "${GITHUB_WORKSPACE}/output" + # SECURITY: Never expose --no-post-script as a workflow input. + # Post-scripts enforce secret scanning, protected-path blocks, + # and review-downgrade controls. Skipping them in CI bypasses + # all post-push security gates. fullsend run "${AGENT}" \ --fullsend-dir "${GITHUB_WORKSPACE}" \ --output-dir "${GITHUB_WORKSPACE}/output" \