mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-20 19:52:23 +02:00
75 lines
2.8 KiB
Python
75 lines
2.8 KiB
Python
"""Strip secrets before anything enters a .swarm. Two layers: closure scrubs
|
|
every payload + text body, and ziputil.pack refuses to write if anything denied
|
|
slipped through. Over-redacting a bundle is fine; shipping a stranger your API
|
|
key is not."""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
# Substrings that mark a field name as secret (matched case-insensitively).
|
|
P_DENY_SUBSTRINGS = (
|
|
"api_key", "apikey", "secret", "password", "passwd", "credential", "oauth",
|
|
"bearer", "subscription_token", "access_token", "refresh_token",
|
|
"session_token", "auth_token", "private_key",
|
|
)
|
|
|
|
# Exact field names that are sensitive or per-install identity (the substring pass alone would miss these).
|
|
P_DENY_EXACT = {
|
|
"token", "installation_id", "user_id", "free_trial_token",
|
|
"free_trial_remaining", "free_trial_runs_limit", "openswarm_bearer_token",
|
|
"openswarm_usage_cached", "connected_account_email", "oauth_tokens",
|
|
"credentials", "sdk_session_id",
|
|
}
|
|
|
|
# The secret-shape scanner moved to backend.common so skills + settings reuse it without reaching into swarm; re-exported here so ziputil/closure keep their API.
|
|
from backend.common.secret_scan import ( # noqa: E402
|
|
REDACTED,
|
|
find_secrets_in_files,
|
|
looks_secret as p_looks_secret,
|
|
redact_secret_shapes as scrub_text,
|
|
)
|
|
|
|
|
|
def is_denied_key(key: str) -> bool:
|
|
k = key.lower()
|
|
if k in P_DENY_EXACT:
|
|
return True
|
|
return any(sub in k for sub in P_DENY_SUBSTRINGS)
|
|
|
|
|
|
def scrub_payload(value: Any) -> Any:
|
|
"""Recursively drop denied keys and redact secret-shaped strings in a
|
|
JSON-able structure. Returns a new structure; never mutates the input."""
|
|
if isinstance(value, dict):
|
|
out: dict[str, Any] = {}
|
|
for k, v in value.items():
|
|
if isinstance(k, str) and is_denied_key(k):
|
|
continue
|
|
out[k] = scrub_payload(v)
|
|
return out
|
|
if isinstance(value, list):
|
|
return [scrub_payload(v) for v in value]
|
|
if isinstance(value, str):
|
|
return scrub_text(value)
|
|
return value
|
|
|
|
|
|
def find_denied_keys(value: Any, p_path: str = "") -> list[str]:
|
|
"""Audit used by ziputil.pack as the last line of defense: the paths of any
|
|
denied key still present. Empty list means clean."""
|
|
found: list[str] = []
|
|
if isinstance(value, dict):
|
|
for k, v in value.items():
|
|
here = f"{p_path}.{k}" if p_path else str(k)
|
|
if isinstance(k, str) and is_denied_key(k):
|
|
found.append(here)
|
|
found.extend(find_denied_keys(v, here))
|
|
elif isinstance(value, list):
|
|
for i, v in enumerate(value):
|
|
found.extend(find_denied_keys(v, f"{p_path}[{i}]"))
|
|
return found
|
|
|
|
|
|
# _looks_secret + find_secrets_in_files now come from backend.common.secret_scan (imported at the top); kept re-exported so ziputil's audit import is unchanged.
|