diff --git a/strix/config/config.py b/strix/config/config.py index 4cf6e600..6f98d920 100644 --- a/strix/config/config.py +++ b/strix/config/config.py @@ -141,11 +141,10 @@ class Config: cls.save({"env": env_vars}) applied = {} - llm_vars = cls._llm_env_vars() for var_name, var_value in env_vars.items(): if var_name in cls.tracked_vars(): - # LLM vars in cli-config.json always win over shell env - if var_name in llm_vars or force or var_name not in os.environ: + # Shell env wins unless --force or the var is not set in shell. + if force or var_name not in os.environ: os.environ[var_name] = var_value applied[var_name] = var_value diff --git a/strix/interface/checkpoint_restore.py b/strix/interface/checkpoint_restore.py index 5a9f1dc0..b4a2f830 100644 --- a/strix/interface/checkpoint_restore.py +++ b/strix/interface/checkpoint_restore.py @@ -34,12 +34,11 @@ def restore_sub_agents(checkpoint_data: Any, llm_config: Any) -> list[str]: def _depth(aid: str) -> int: if aid in _memo: return _memo[aid] + # Mark before recursing to break any cycle in corrupted checkpoints. + _memo[aid] = 0 parent = sub_agent_states.get(aid, {}).get("parent_id") - _memo[aid] = ( - 0 - if (parent is None or parent not in sub_agent_states) - else 1 + _depth(parent) - ) + if parent is not None and parent in sub_agent_states: + _memo[aid] = 1 + _depth(parent) return _memo[aid] restored_ids: list[str] = [] diff --git a/strix/interface/cli.py b/strix/interface/cli.py index 3950e693..7bf88f91 100644 --- a/strix/interface/cli.py +++ b/strix/interface/cli.py @@ -266,9 +266,13 @@ async def run_cli(args: Any) -> None: # noqa: PLR0915 return if _checkpoint_saved.is_set(): return + agent_instance = _agent_ref[0] + # Skip if the scan already completed successfully — the checkpoint + # was deleted in base_agent.py and there is nothing to resume. + if getattr(agent_instance.state, "completed", False): + return _checkpoint_saved.set() try: - agent_instance = _agent_ref[0] checkpoint_manager.save( agent_instance.state, tracer, diff --git a/strix/interface/tui.py b/strix/interface/tui.py index 09b048f8..c30cca7f 100644 --- a/strix/interface/tui.py +++ b/strix/interface/tui.py @@ -814,6 +814,9 @@ class StrixTUIApp(App): # type: ignore[misc] return if _checkpoint_saved.is_set(): return + # Skip if the scan already completed — nothing to resume. + if getattr(agent.state, "completed", False): + return _checkpoint_saved.set() try: mgr.save( @@ -2021,14 +2024,16 @@ class StrixTUIApp(App): # type: ignore[misc] _agent = getattr(self, "_current_agent", None) if _mgr and _agent: import contextlib - with contextlib.suppress(Exception): - _mgr.save( - _agent.state, - self.tracer, - self.scan_config, - self.agent_config.get("target_hash", ""), - _agent.max_iterations, - ) + # Only save if the scan was interrupted, not if it finished cleanly. + if not getattr(_agent.state, "completed", False): + with contextlib.suppress(Exception): + _mgr.save( + _agent.state, + self.tracer, + self.scan_config, + self.agent_config.get("target_hash", ""), + _agent.max_iterations, + ) self.tracer.cleanup() diff --git a/strix/skills/vulnerabilities/clickjacking.md b/strix/skills/vulnerabilities/clickjacking.md new file mode 100644 index 00000000..18b4219c --- /dev/null +++ b/strix/skills/vulnerabilities/clickjacking.md @@ -0,0 +1,71 @@ +--- +name: clickjacking +description: Clickjacking testing covering UI redressing, frame embedding, and X-Frame-Options / CSP bypass techniques +--- + +# Clickjacking + +Clickjacking (UI redressing) tricks users into clicking hidden or disguised UI elements by overlaying transparent iframes on top of legitimate pages. + +## Attack Surface + +**Targets** +- Pages that perform sensitive actions (fund transfers, account changes, password resets, OAuth authorization, social actions) +- Pages missing `X-Frame-Options` or `Content-Security-Policy: frame-ancestors` + +**Defenses to Bypass** +- `X-Frame-Options: DENY / SAMEORIGIN` +- `Content-Security-Policy: frame-ancestors 'none' / 'self'` +- Frame-busting JavaScript + +## Testing Methodology + +### Step 1 – Check Headers +``` +curl -s -I https://target.com | grep -i "x-frame-options\|frame-ancestors" +``` +Missing or misconfigured headers indicate framing is allowed. + +### Step 2 – Attempt Embedding +```html + +``` +If the page renders inside the iframe, the site is vulnerable. + +### Step 3 – Frame-Buster Bypass +If JavaScript frame-busting is used (e.g., `if (top !== self) top.location = self.location`): +- Use `sandbox` attribute to disable JS: `