Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 10 additions & 6 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ var (
InstallDir string // "" means default (~/.stepsecurity); non-empty makes the agent put all its files (logs, hook errors, future state) under this directory. Bootstrap config.json itself stays at the legacy location. Per-run opt-out is the CLI flag --install-dir=. Resolution: --install-dir flag > STEPSECURITY_HOME env > this field > default — see internal/paths.
// UseLegacyPackageScan, when true, disables the scan-state delta-upload
// optimization for npm and Python project scans — every run re-uploads
// the full snapshot as in pre-1.13 agents. Default false = optimized.
// Environment override: STEPSEC_DISABLE_SCAN_STATE=1 takes precedence.
UseLegacyPackageScan bool
// the full snapshot as in pre-1.13 agents.
//
// Defaults to true: the delta protocol is gated OFF until the agent-api
// side ships. Set use_legacy_package_scan=false in config.json (or
// STEPSEC_ENABLE_SCAN_STATE=1) to opt back in. STEPSEC_DISABLE_SCAN_STATE=1
// always forces legacy.
UseLegacyPackageScan = true
)

// MaxExecutionDuration is the whole-process execution-watchdog limit
Expand Down Expand Up @@ -59,7 +63,7 @@ type ConfigFile struct {
LogLevel string `json:"log_level,omitempty"`
InstallDir string `json:"install_dir,omitempty"`
MaxExecutionDuration string `json:"max_execution_duration,omitempty"`
UseLegacyPackageScan bool `json:"use_legacy_package_scan,omitempty"`
UseLegacyPackageScan *bool `json:"use_legacy_package_scan,omitempty"`
}

// userConfigDir returns ~/.stepsecurity — the per-user config location.
Expand Down Expand Up @@ -192,8 +196,8 @@ func Load() {
if cfg.MaxExecutionDuration != "" && MaxExecutionDuration == "" {
MaxExecutionDuration = cfg.MaxExecutionDuration
}
if cfg.UseLegacyPackageScan {
UseLegacyPackageScan = true
if cfg.UseLegacyPackageScan != nil {
UseLegacyPackageScan = *cfg.UseLegacyPackageScan
}
}

Expand Down
47 changes: 42 additions & 5 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ func TestConfigFile_InstallDir_JSONRoundTrip(t *testing.T) {
}

func TestConfigFile_UseLegacyPackageScan_JSONRoundTrip(t *testing.T) {
in := ConfigFile{UseLegacyPackageScan: true}
legacy := true
in := ConfigFile{UseLegacyPackageScan: &legacy}
data, err := json.Marshal(in)
if err != nil {
t.Fatal(err)
Expand All @@ -197,18 +198,29 @@ func TestConfigFile_UseLegacyPackageScan_JSONRoundTrip(t *testing.T) {
if err := json.Unmarshal(data, &out); err != nil {
t.Fatal(err)
}
if !out.UseLegacyPackageScan {
t.Errorf("UseLegacyPackageScan round-trip = false, want true")
if out.UseLegacyPackageScan == nil || !*out.UseLegacyPackageScan {
t.Errorf("UseLegacyPackageScan round-trip = %v, want true", out.UseLegacyPackageScan)
}

// False/absent is omitted (default optimized path is the unmarked state).
// An explicit false must survive the round trip — it's how a config opts
// the (default-off) delta protocol back on.
enabled := false
data, err = json.Marshal(ConfigFile{UseLegacyPackageScan: &enabled})
if err != nil {
t.Fatal(err)
}
if !bytes.Contains(data, []byte(`"use_legacy_package_scan":false`)) {
t.Errorf("explicit false should serialize, not be omitted: %s", data)
}

// Absent (nil) is omitted — the field falls back to the package default.
empty := ConfigFile{}
data, err = json.Marshal(empty)
if err != nil {
t.Fatal(err)
}
if bytes.Contains(data, []byte("use_legacy_package_scan")) {
t.Errorf("default-false should be omitted: %s", data)
t.Errorf("nil should be omitted: %s", data)
}
}

Expand Down Expand Up @@ -236,3 +248,28 @@ func TestLoad_UseLegacyPackageScan_AppliedFromFile(t *testing.T) {
t.Errorf("Load did not propagate use_legacy_package_scan from config.json")
}
}

func TestLoad_UseLegacyPackageScan_FalseReEnablesFromFile(t *testing.T) {
// Default is legacy-on (true). An explicit false in config.json must flip
// the package var back to the delta protocol.
prev := UseLegacyPackageScan
t.Cleanup(func() { UseLegacyPackageScan = prev })
UseLegacyPackageScan = true

dir := t.TempDir()
t.Setenv("HOME", dir)
t.Setenv("STEPSECURITY_HOME", dir)
cfgPath := filepath.Join(dir, ".stepsecurity", "config.json")
if err := os.MkdirAll(filepath.Dir(cfgPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(cfgPath, []byte(`{"use_legacy_package_scan":false}`), 0o600); err != nil {
t.Fatal(err)
}

Load()

if UseLegacyPackageScan {
t.Errorf("explicit use_legacy_package_scan=false did not re-enable the delta protocol")
}
}
19 changes: 14 additions & 5 deletions internal/telemetry/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,15 +387,24 @@ func Run(exec executor.Executor, log *progress.Logger, cfg *cli.Config) (err err
}
endPhase(phaseCtx, phaseCancel, tracker, log, "device_info")

// Per-device scan state for the delta-upload protocol. Three opt-outs:
// - config.UseLegacyPackageScan = true (persistent, set in config.json)
// - STEPSEC_DISABLE_SCAN_STATE=1 (env, incident response)
// Per-device scan state for the delta-upload protocol. Gated OFF by
// default (config.UseLegacyPackageScan defaults true) until the agent-api
// side ships. Resolution, in order:
// - STEPSEC_DISABLE_SCAN_STATE=1 (env kill switch, always wins)
// - STEPSEC_ENABLE_SCAN_STATE=1 (env test opt-in)
// - config.UseLegacyPackageScan (persistent, set in config.json)
// - paths.Home() unresolvable (no place to write the file)
// Any of the three leaves scanState nil and the run behaves as pre-1.13.
// A disabled gate leaves scanState nil and the run behaves as pre-1.13.
var scanState *state.State
var scanStatePath string
var scanStateFullSync bool
scanStateDisabled := config.UseLegacyPackageScan || os.Getenv("STEPSEC_DISABLE_SCAN_STATE") == "1"
scanStateDisabled := config.UseLegacyPackageScan
if os.Getenv("STEPSEC_ENABLE_SCAN_STATE") == "1" {
scanStateDisabled = false
}
if os.Getenv("STEPSEC_DISABLE_SCAN_STATE") == "1" {
scanStateDisabled = true
}
if !scanStateDisabled {
scanStatePath = paths.ScanStateFile()
if scanStatePath != "" {
Expand Down
Loading