Skip to content

[spark-compete wave 5] security cross-cut (stacked)#1465

Merged
vibeforge1111 merged 20 commits into
masterfrom
spark-compete/wave5-security-crosscut
Jun 26, 2026
Merged

[spark-compete wave 5] security cross-cut (stacked)#1465
vibeforge1111 merged 20 commits into
masterfrom
spark-compete/wave5-security-crosscut

Conversation

@vibeforge1111

Copy link
Copy Markdown
Owner

Spark Compete — Wave 5 (security cross-cut), stacked on spark-compete/wave4-memory-persona-voice

Highest-value spark-cli security set (4 commits). Wave-5 delta only.

Commits

#1417/#1418/#245 are interim_until_migration (re-home approval rules into the Governor on migration).

On-merge points

mrxlolcat 175 · ifeoluwaaj 111 · 4gjnbzb4zf-sudo 34 · binance1230 26 · FianKuong 14

Excluded

⚠️ HELD for security-owner sign-off (NOT in this PR)

Verified: 230/16 delta, no churn, no conflict markers, py_compile clean, 12 new tests green (2 pre-existing base-branch test failures, not from this wave). Draft — gated on CI + approval.

🤖 Generated with Claude Code

Meta Alchemist and others added 20 commits June 24, 2026 12:59
…ormed-script tolerance, unicode injection defense

Consolidates spark-compete Wave-1 input-hardening PRs:
- #1432 sanitize module name from git URL (path traversal) — @ifeoluwaaj
- #1434 validate column names vs allowlist before SQL interpolation — @ifeoluwaaj
- #1423 tolerate malformed package scripts — @Aeyod7
- #1425 unicode normalization in prompt-injection scanner (homoglyph evasion) — @ifeoluwaaj

Maintainer completion: added tests/test_prompt_injection_unicode.py covering
normalize_unicode + homoglyph-obfuscated injection detection (#1425 headline shipped untested).

Co-authored-by: ifeoluwaaj <ifeoluwaaj@users.noreply.github.com>
Co-authored-by: Aeyod7 <Aeyod7@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ntity-mutation commands

Consolidates spark-compete Wave-1 approval PRs (@mrxlolcat):
- #1440 require approval for docker exec, nsenter, chroot (container_privilege_escalation)
- #1441 require approval for user/group/credential mutations (identity_access_mutation)

adopt_interim: this CLI-surface approval classifier is the still-live gate; on the
CLI->harness-core migration it must be re-homed into the Governor approval classifier
(authority plane), not left as string matching. No data migration.

Maintainer completion: hand-merged #1441's classifier block (line-drift conflict with
#1440 in approval.py/test_cli.py) and added tests/test_approval_wave1.py.

Co-authored-by: mrxlolcat <mrxlolcat@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ace dedup, SPARK_HOME write-guard, restart/SSH/install fixes

Consolidates spark-compete Wave-1 CLI-robustness PRs (resurrected from reviewed-but-unadopted):
- #239 accurate error when provider key is managed externally — @mrxlolcat
- #241 save partial doctor report even when LLM probe fails — @mrxlolcat
- #238 deduplicate trace repair queue entries — @mrxlolcat
- #246 exclude SPARK_HOME from write_denied_prefixes (unblocks live start/update) — @mrxlolcat
- #81  restart exit code, stop_module PID safety, SSH JSON error, install.sh word-split — @binance1230
- #210 Windows installer preflight when python3 app alias fails — @codex

Maintainer completion: 3-way rebased onto current master; stripped the bundled
registry.json commit-pin bumps (unauthorized attestation-pin regression).

Co-authored-by: mrxlolcat <mrxlolcat@users.noreply.github.com>
Co-authored-by: binance1230 <binance1230@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…fallback

Consolidates spark-compete Wave-1 CLI-UX PRs (resurrected):
- #242 helpful menus for bare 'spark os' / 'spark providers' / 'spark support' — @mrxlolcat
- #240 helpful message for bare 'spark recommend' + clarify --desktop help — @mrxlolcat
- #283 Android/Termux Desktop fallback when ~/Desktop does not exist — @johncrossu

Maintainer completion: applied #240 paired with #242's required=False relax (else the
guard is dead code); stripped #283's bundled registry.json bump.

Co-authored-by: mrxlolcat <mrxlolcat@users.noreply.github.com>
Co-authored-by: johncrossu <johncrossu@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…acing CLI output

Consolidates nine spark-compete Wave-1 path-redaction PRs (all @Esc1200) into one
coherent pass — credited as a single systemic group (ruleset v2 §5.2), not nine fixes:
- #1406 secret file path leak in error message
- #1408 redact paths from spawn failure errors
- #1421 redact SPARK_HOME from purge safety error
- #1422 generic text for manifest_path in SystemExit messages
- #1424 redact browser-use print paths (basename reference kept)
- #1429 remove internal path leaks from CLI prints
- #1430 redact hook/log paths from list output
- #1409 redact internal paths from gaps markdown report
- #1426 remove operator/log path leaks

Maintainer completion:
- narrowed _PATH_REDACT_RE to anchor POSIX paths to known roots + ~ + Windows drives,
  so URL paths (/api/v2/users) and slashy text (and/or, 3/4) are no longer over-redacted;
- removed compete-packet-operator-path-leak.json accidentally committed in #1426.

Co-authored-by: Esc1200 <Esc1200@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…--lines help, uninstall-feedback + list/output cleanups

Consolidates remaining spark-compete Wave-1 CLI-output PRs:
- #1428 inspect_builder_event_samples top_trace_refs cap — @4gjnbzb4zf-sudo
- #1410 Builder overlap probes report matched count without disclosing the match — @4gjnbzb4zf-sudo
- #1407 'spark live logs --lines' help text — @4gjnbzb4zf-sudo
- #1427 remove internal module paths from CLI list/status output — @Esc1200
- #1439 preserve uninstall feedback when a named target hits empty registry — @4gjnbzb4zf-sudo

Maintainer completion:
- #1407/#1410: dropped ALL bundled registry.json commit-pin bumps (unauthorized
  attestation regression); kept only the cli.py help string / probe_cap fields;
- #1427: dropped the leaked trailing module.path column instead of duplicating the
  name column (the PR's {module.path}->{module.name} swap created a dup);
- #1439: hardened args.target access with getattr(args, "target", None).

Co-authored-by: 4gjnbzb4zf-sudo <4gjnbzb4zf-sudo@users.noreply.github.com>
Co-authored-by: Esc1200 <Esc1200@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ding step 4

PR #243 (resurrect_adopt): two Telegram first-run UX fixes in cli.py.

- `spark smoke` with no subcommand now prints a helpful subcommand menu
  (smoke_command no longer required=True) instead of an argparse error,
  matching the bare-subcommand menu pattern used elsewhere in the CLI.
- Onboarding checklist step 4 now notes that when the LLM key is managed
  externally (e.g. by a host platform), the user can skip the PING_OK
  provider test and confirm readiness with `spark providers status`.

Co-authored-by: mrxlolcat <mrxlolcat@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ls show non-zero cards

build_capability_cards now accepts an optional module_capabilities list and
emits a synthetic, explicitly-untrusted card for any module that declares
provides_capabilities in spark.toml but has no creator-system or
specialization-path surface yet. Wired through build_capability_catalog so a
fresh telegram-starter install surfaces non-zero capability cards instead of an
empty catalog. Synthetic cards stay trust_status=untrusted /
proof_state=proof_incomplete and add no new authority.

Salvaged from spark-cli#225 per maintainer review: only the
build_capability_cards + build_capability_catalog wiring (~50 lines) is taken.
The PR's redact_secret_surface_logs edit (inserts an early return ahead of dead
code) and the unrelated build_authority_view path-resolution change are
discarded.

Co-authored-by: Isaaco3349 <Isaaco3349@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nstall

Salvaged the build_memory_review_queue severity/wording hunk from PR #229:
when the Builder memory movement export status is "missing"/"" (a fresh
install that never had an export), surface it as a "warning" with a
"Configure ..." action rather than a "critical" "Restore ..." action that
falsely implies a regression. Non-missing failure states still report
"critical".

Per the maintainer review, only the memory-review-queue hunk was salvaged;
the bundled redact_secret_surface_logs early-return rewrite, the
build_authority_view spark_home change (already in HEAD), and the
trace/capability-card hunks were discarded (covered cleanly by #238/#225
and #266).

Co-authored-by: Isaaco3349 <Isaaco3349@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…_health_status

Salvaged the build_trace_repair_queue health-status hunk from PR #266: each
repair row's current_health_status now reflects that producer's own temporal
state ("stale_missing_trace_ref" when the latest known row predates the active
window, "latest_clean" when the newest row already carries a trace_ref) instead
of always echoing the global trace-window status. Falls through to the global
current_health status when neither per-item state applies.

Per the maintainer review, only the trace-repair health hunk was salvaged.
The PR's seen_ids dedup is already covered in HEAD by the equivalent
seen_repair_keys (component, event_type) guard. The build_authority_view
spark_home change is already in HEAD; the redact_secret_surface_logs rewrite
and capability-card/memory-severity hunks were dropped as out-of-scope
(memory severity landed separately from #229). The PR's stray 15-space
indentation on this line was corrected to 16.

Co-authored-by: Isaaco3349 <Isaaco3349@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…npm subcommand allowlist

PR #1419 (ifeoluwaaj): write SSH known_hosts via mkstemp + os.replace so a
concurrent reader never observes a truncated/partial host-key file (TOCTOU
race). Mirrors the existing atomic-write pattern in ssh_targets persistence.
The companion write_env_file newline-sanitization from this PR is already
present earlier in the stack, so only the ssh.py atomic write is net-new here.

PR #150 (FianKuong): access_lane_payload iterated load_ssh_targets() as if it
were a list, but it returns dict[str, SshTarget]; the comprehension was binding
`target` to the string key and every `.host_key_status` access would raise.
Iterate .values() so trusted-target detection actually works.

PR #300 (mrxlolcat): restrict npm in module install commands to the
install/ci subcommands only, rejecting arbitrary npm subcommands (run-script,
exec, etc.) that could execute attacker-controlled lifecycle hooks.

Co-authored-by: ifeoluwaaj <ifeoluwaaj@users.noreply.github.com>
Co-authored-by: FianKuong <FianKuong@users.noreply.github.com>
Co-authored-by: mrxlolcat <mrxlolcat@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ost_execution, fix support log message

PR #187 (4gjnbzb4zf-sudo) + PR #299 (mrxlolcat) — same systemic concern,
consolidated: normalize_fixture_finding downgraded embedded-private-key
findings to "low" when they appeared on a fixture/test path. A real private
key checked into a test file is still a real, exfiltratable key. Drop
embedded-private-key from the downgrade set; network-exfiltration and
environment-dump fixture downgrades are unchanged.

PR #1417 (ifeoluwaaj): add high_cost_execution to
APPROVAL_ENFORCED_ACTION_CLASSES. The class was already emitted by the
approval classifier (security/approval.py) but was never in the enforced set,
so high-cost-execution decisions never actually required approval. Enforce it.
harness_core=interim_until_migration: re-home into Governor on migration.

PR #236 (mrxlolcat): the support-bundle summary always printed "Logs are
excluded unless you used --include-logs" even when --include-logs WAS passed,
misleading the user about what the bundle contains. Branch on args.include_logs
and warn to review log excerpts when they are included.

Co-authored-by: 4gjnbzb4zf-sudo <4gjnbzb4zf-sudo@users.noreply.github.com>
Co-authored-by: mrxlolcat <mrxlolcat@users.noreply.github.com>
Co-authored-by: ifeoluwaaj <ifeoluwaaj@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ssify uninstall --all, flag chmod/chown + curl/wget file-writes

PR #298 (mrxlolcat): approval decisions echoed target_display verbatim into
user-facing output, so a secret embedded in a command target (e.g. an inline
token) could be printed back. Route target_display through _redact_display and
expand SECRET_LIKE_PATTERN to cover GitHub PATs, AWS access keys (AKIA/ASIA),
and Slack tokens in addition to the existing OpenAI/Anthropic keys, JWTs, and
Telegram bot tokens.

PR #245 (mrxlolcat): classify `spark uninstall --all` as
destructive_filesystem/high — it removes every installed module and its
generated config, which is unrecoverable without reinstalling, but was
previously unguarded (only --purge-home required approval).
harness_core=interim_until_migration: re-home into Governor on migration.

PR #1418 (ifeoluwaaj) — adjusted per maintainer note:
- Flag chmod/chown as destructive_filesystem/high (permission/ownership
  changes enable privilege escalation).
- Flag curl/wget that writes downloaded content to disk. Per the note, the
  wget rule triggers on default-output (wget writes to the cwd with no flag);
  curl triggers only with -o/--output/-O.
- The note's "curl-pipe-to-shell detection" is already covered by the existing
  remote_code_execution rule, so the new file-write rule is guarded to defer to
  it (and to the existing upload/exfiltration rule) — it never downgrades those
  higher-severity classes.
- Added unit tests mirroring test_approval_classifier_flags_docker_privilege_escalation:
  chmod/chown, curl/wget file-write, plain-GET-is-not-file-write, and
  pipe-to-shell-stays-RCE regression guards.
harness_core=interim_until_migration: re-home into Governor on migration.

Co-authored-by: mrxlolcat <mrxlolcat@users.noreply.github.com>
Co-authored-by: ifeoluwaaj <ifeoluwaaj@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…DPAPI guard, pip-index lock, docker tmpfs, redact-first truncation

PR #87 (binance1230) — landed selectively per maintainer note (fixes 1, 2, 3,
5-adjusted, 6 adopted; fix 4 dropped):

- Fix 1 (entrypoint.sh): pass the OpenAI key to `codex login` via a 0600
  mktemp file + stdin redirect instead of `printenv KEY | codex login`, so the
  key never appears in the process argument list / `ps` output. The temp file
  is removed immediately after.
- Fix 2 (dpapi_unprotect): a DPAPI-prefixed secret on a non-Windows host used
  to silently return the still-encrypted blob (caller gets garbage). Restructure
  so the prefix is checked first, then raise a clear OSError on non-Windows
  instructing the user to re-enter the secret. (The PR placed this check after
  the early return, making it dead code; corrected here.)
- Fix 3 (install_command_argv): reject custom package-index URLs
  (--index-url / --extra-index-url) on the pip and `uv pip` install paths via
  _reject_custom_pip_index, preventing dependency-confusion installs from an
  attacker-controlled index.
- Fix 5 (safe_short_string): keep the broadened redaction keyword set
  (password/passwd/credential/private_key/auth + Bearer/Basic) but PRESERVE the
  redact-on-full-string-BEFORE-truncate ordering (the PR reordered to
  truncate-first, which can leak the leading chars of a long secret). Added a
  regression test proving truncate-first would have leaked.
- Fix 6 (docker-compose.vps.yml): mount /home/spark as a size-limited nosuid
  tmpfs so the container has a writable home without a persistent on-disk one.

Fix 4 (table-name SQL identifier sanitization) intentionally NOT taken here —
coordinate with open PR #1434 which handles the same-file SQL-identifier class,
to avoid double-handling. The PR's SQL-table-name test is likewise dropped.

Tests: pip-index rejection (custom + extra), default-pip passthrough, DPAPI
cross-platform raise + non-DPAPI passthrough, and safe_short_string redaction
(api_key, Bearer, broadened keywords, redact-before-truncate).

Co-authored-by: binance1230 <binance1230@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-out; regen installer manifest

Three CI-green code fixes for the wave5 security crosscut:

- runtime_policy: the npm subcommand allowlist was placed on the trusted
  module-boot (runtime/start) path, which legitimately needs `npm run <script>`
  to launch Node modules. PR #300's hardening was meant for untrusted *install*
  commands (handled separately in install_command_argv). Allow `run` on the
  runtime path so module start works again; install/ci stay allowed there too.

- cli (scan_module_trust): "never downgrade embedded-private-key" was too broad
  and broke the redaction-test-fixture carve-out. A bare key in a test file is
  still flagged critical, but a placeholder key passed straight into a redaction
  helper (redact/redactText/scrub/...) inside a fixture path is a sample input,
  not installed key material, so it is downgraded to low again. Real keys in
  keys.txt and elsewhere remain critical.

- installer-manifest.json: install.ps1 was hardened in wave1 (Windows preflight
  robustness) but the integrity manifest was never regenerated, so the committed
  sha256 no longer matched. Regenerated from the current script (hash-only diff).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ehavior

These tests asserted pre-hardening behavior that was intentionally and correctly
changed; updated to assert the new contract (no behavior weakened):

- VBS startup script: assert the `%%`-escaped form (vbs_string now escapes `%` so
  cmd.exe does not re-expand env-var tokens in the launched command).
- os capability cards: a module declaring provides_capabilities now surfaces one
  synthetic, explicitly-untrusted card (wave3 fallback) instead of an empty
  catalog; assert card_count == 1 with untrusted / proof_incomplete.
- bare `spark os`: migrated to a helpful runtime menu (subparsers required=False)
  that prints subcommands and returns exit code 1 rather than raising an argparse
  SystemExit; split into a dedicated test for the menu behavior.
- stop_module: now does a liveness pre-check (os.kill(pid, 0)) before signalling;
  mock os.kill so the SIGTERM-then-wait and SIGTERM->SIGKILL escalation paths run.
- builder activation note: no longer leaks the internal filesystem path (wave1
  output redaction); assert the redacted note and that the path stays out of it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…li.py

gitleaks flagged fake secrets used in redaction/secret-drop tests (sk-secret,
ui-secret, HF_TOKEN, a null-byte DPAPI blob). No real secrets. Same class as the
existing .gitleaksignore entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The wave branches were cut before the registry-pin tag-hardening landed on master,
so they carried a stale spark-voice-comms pin at refs/heads/main@21a9467 (now drifted),
failing 'verify --registry-pins'. Adopt master's immutable tag-pin
(refs/tags/spark-pin-2026-06-25@f4783cc). No code change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… (acknowledged growth; extraction deferred to fleet-discipline round)
@vibeforge1111 vibeforge1111 changed the base branch from spark-compete/wave4-memory-persona-voice to master June 26, 2026 13:18
@vibeforge1111 vibeforge1111 marked this pull request as ready for review June 26, 2026 13:20
@vibeforge1111 vibeforge1111 reopened this Jun 26, 2026
@vibeforge1111 vibeforge1111 merged commit 2b04226 into master Jun 26, 2026
7 checks passed
@vibeforge1111 vibeforge1111 deleted the spark-compete/wave5-security-crosscut branch June 26, 2026 14:53
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.

1 participant