diff --git a/strix/agents/StrixAgent/system_prompt.jinja b/strix/agents/StrixAgent/system_prompt.jinja index b832cf6..e916b6d 100644 --- a/strix/agents/StrixAgent/system_prompt.jinja +++ b/strix/agents/StrixAgent/system_prompt.jinja @@ -115,6 +115,7 @@ WHITE-BOX TESTING (code provided): - Static coverage floor: execute at least one structural AST mapping pass (`sg` and/or Tree-sitter) per repository and keep artifact output - Static coverage target per repository: run one `semgrep` pass, one secrets pass (`gitleaks` and/or `trufflehog`), one `trivy fs` pass, and one AST-structural pass (`sg` and/or Tree-sitter); if any are skipped, record why in the shared wiki - Keep AST artifacts bounded and high-signal: scope to relevant paths/hypotheses, avoid whole-repo generic function dumps +- AST target selection rule: build `sg-targets.txt` from `semgrep.json` scope first (`paths.scanned`, fallback to unique `results[].path`), then run `xargs ... sg run` against that file list. Only use path-heuristic fallback if semgrep scope is unavailable, and log fallback reason in the wiki. - Shared memory: Use notes as shared working memory; discover wiki notes with `list_notes`, then read the selected one via `get_note(note_id=...)` before analysis - Before `agent_finish`/`finish_scan`, update the shared repo wiki with scanner summaries, key routes/sinks, and dynamic follow-up plan - Dynamic: Run the application and test live to validate exploitability diff --git a/strix/skills/coordination/source_aware_whitebox.md b/strix/skills/coordination/source_aware_whitebox.md index 037b180..58f0a8b 100644 --- a/strix/skills/coordination/source_aware_whitebox.md +++ b/strix/skills/coordination/source_aware_whitebox.md @@ -14,6 +14,8 @@ Increase white-box coverage by combining source-aware triage with dynamic valida ## Recommended Workflow 1. Build a quick source map before deep exploitation, including at least one AST-structural pass (`sg` or `tree-sitter`) scoped to relevant paths. + - For `sg` baseline, derive `sg-targets.txt` from `semgrep.json` scope first (`paths.scanned`, fallback to unique `results[].path`) and run `xargs ... sg run` on that list. + - Only fall back to path heuristics when semgrep scope is unavailable, and record the fallback reason in the repo wiki. 2. Run first-pass static triage to rank high-risk paths. 3. Use triage outputs to prioritize dynamic PoC validation. 4. Keep findings evidence-driven: no report without validation. diff --git a/strix/tools/notes/notes_actions.py b/strix/tools/notes/notes_actions.py index bcbdf58..450ff35 100644 --- a/strix/tools/notes/notes_actions.py +++ b/strix/tools/notes/notes_actions.py @@ -103,9 +103,12 @@ def _ensure_notes_loaded() -> None: notes_path = _get_notes_jsonl_path() if notes_path: _notes_storage.update(_load_notes_from_jsonl(notes_path)) - for note_id, note in _notes_storage.items(): - if note.get("category") == "wiki": - _persist_wiki_note(note_id, note) + try: + for note_id, note in _notes_storage.items(): + if note.get("category") == "wiki": + _persist_wiki_note(note_id, note) + except OSError: + pass _loaded_notes_run_dir = run_dir_key diff --git a/tests/tools/test_notes_wiki.py b/tests/tools/test_notes_wiki.py index e27ce58..31031e3 100644 --- a/tests/tools/test_notes_wiki.py +++ b/tests/tools/test_notes_wiki.py @@ -170,3 +170,45 @@ def test_append_note_content_appends_delta(tmp_path: Path, monkeypatch) -> None: finally: _reset_notes_state() set_global_tracer(previous_tracer) # type: ignore[arg-type] + + +def test_list_and_get_note_handle_wiki_repersist_oserror_gracefully( + tmp_path: Path, monkeypatch +) -> None: + monkeypatch.chdir(tmp_path) + _reset_notes_state() + + previous_tracer = get_global_tracer() + tracer = Tracer("wiki-repersist-oserror-run") + set_global_tracer(tracer) + + try: + created = notes_actions.create_note( + title="Repo wiki", + content="initial wiki content", + category="wiki", + tags=["repo:demo"], + ) + assert created["success"] is True + note_id = created["note_id"] + assert isinstance(note_id, str) + + _reset_notes_state() + + def _raise_oserror(*_args, **_kwargs) -> None: + raise OSError("disk full") + + monkeypatch.setattr(notes_actions, "_persist_wiki_note", _raise_oserror) + + listed = notes_actions.list_notes(category="wiki") + assert listed["success"] is True + assert listed["total_count"] == 1 + assert listed["notes"][0]["note_id"] == note_id + + fetched = notes_actions.get_note(note_id=note_id) + assert fetched["success"] is True + assert fetched["note"]["note_id"] == note_id + assert fetched["note"]["content"] == "initial wiki content" + finally: + _reset_notes_state() + set_global_tracer(previous_tracer) # type: ignore[arg-type]