mirror of
https://github.com/nearai/ironclaw.git
synced 2026-09-02 23:56:24 +08:00
* feat(credentials): path-based credential matching for per-endpoint auth Add `path_patterns` field to `CredentialMapping` to scope credentials to specific URL path prefixes on a host. When set, the request path must match a prefix at a segment boundary (`/` or `?`). When empty (default), credentials match all paths on the host — fully backwards compatible. Key changes: - `CredentialMapping.matches(host, path)` with segment-boundary enforcement - `path_matches_prefix()` rejects `..` traversal, normalizes trailing slashes - `host_matches_pattern()` deduplicated to single source in secrets/types.rs, case-insensitive per RFC 4343 - HTTP tool uses `find_for_url(host, path)` for path-aware credential lookup - Auth manager pre-flight check uses path-aware `find_for_url` - WASM tool/channel wrappers carry `path_patterns` through to injection time - `CredentialMappingSchema`, `SkillCredentialSpec` support `path_patterns` Tests cover segment-boundary attacks, path traversal rejection, case-insensitive host matching, path-scoped injection, and different credentials for different paths on the same host. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(credentials): address PR #2168 review feedback - Narrow `..` rejection in path_matches_prefix to per-segment so legitimate paths like /api/..config are no longer falsely blocked (path_matches_prefix was using path.contains("..")). - Path-scope credential injection in the channels WASM wrapper: ResolvedHostCredential now carries path_patterns and inject_host_credentials honors it, matching the tools-side wrapper. - Add path-aware CredentialInjector API (find_credentials_for_url / inject_for_url); deprecate the host-only variants and SharedCredentialRegistry::find_for_host with #[deprecated] attrs. - Tighten CredentialMappingSchema.path_patterns from Option<Vec<String>> to #[serde(default)] Vec<String>, matching sibling types. - Validate path_patterns in validate_credential_spec: require leading '/', reject empty, reject '..' as a segment. - Expand comment in http.rs documenting why LLM-header blocking is host-scoped (exfil defense) while injection is path-scoped (minimum privilege). Tests: +5 validation cases, +2 injector cases, +2 channel wrapper cases, +2 path_matches_prefix cases covering dot-dot-inside-segment. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(credentials): address PR #2168 round-3 review - secrets/types: reject %2e / %2E in paths (percent-encoded traversal bypass for servers that decode before routing, e.g. IIS/Tomcat) - sandbox/proxy/policy: find_credential now honors path_patterns via CredentialMapping::matches, using request.path (regression test added) - wasm wrappers: extract shared extract_url_path_for_matching helper in secrets/types, with tracing::debug! on URL parse failure; removes duplicated 12-line block between tools/wasm and channels/wasm - ironclaw_skills/validation: factor validate_path_pattern out of validate_credential_spec and reject '?' and '#' in path_patterns (Url::path() strips them, so these silently never match) - tools/wasm/capabilities_schema: plumb the same validate_path_pattern through the WASM manifest loader — bad patterns log as warnings instead of silently failing to match - tools/builtin/http: unit tests for extract_path_from_params (valid, missing url, query+fragment stripping, bare host, malformed) - tests/skill_credential_injection: caller-level tests driving HttpTool::execute with path-scoped credentials — segment boundary and auth-gap-on-non-matching-path (per .claude/rules/testing.md "Test Through the Caller, Not Just the Helper") Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(credentials): address PR #2168 round-4 review - secrets/types: path_matches_prefix now decodes each segment and rejects only dot-segments (literal . or .., plus percent-encoded equivalents like %2e, %2e%2e, mixed case, .%2e, %2e.). Legitimate literal paths with embedded encoded dots — /files/foo%2ebar → foo.bar, /releases/v1%2e2 → v1.2 — are now allowed. Replaces the previous "reject any %2e substring" rule which over-rejected normal filenames. (Firat #3125964627) - secrets/types: add match_specificity(path_patterns, req_path) returning the length of the longest matching prefix (0 if unscoped). Exported pub(crate) for callers that need deterministic credential precedence. - credential_injector + both wasm wrappers: sort matching credentials by ascending path specificity, tie-broken alphabetically on secret_name, before the last-write-wins header merge. The most-specific mapping now wins any header conflict regardless of HashMap iteration order, which fixes nondeterministic winner selection on overlapping mappings. ResolvedHostCredential gains a secret_name field purely for stable tie-breaks (no secret material exposed). (Firat #3125963270) - bridge/auth_manager::check_http_auth: replace "return Ready on first resolved mapping" short-circuit with conjunctive evaluation — every non-optional matched mapping must resolve for Ready. Optional mappings are skipped. Missing required credentials are accumulated and returned as MissingCredentials so endpoints needing bearer + org-header surface the auth gate instead of failing at the wire with a raw 401. (Firat #3125963977) - tools/builtin/http::execute: stop clearing missing_credential on peer success and drop the !injected_any_credential guard. Track the first missing required credential for the 401/403 remediation UX; skip optional mappings. Matches the new auth_manager behavior. Regression tests: - secrets/types: path_matches_prefix_rejects_percent_encoded_dot_segments (adds .%2e and %2e. mixed-form cases), path_matches_prefix_allows_legit_ embedded_encoded_dot (foo%2ebar, v1%2e2), match_specificity_ranks_ longer_prefixes_higher. - tools/wasm/wrapper: test_inject_host_credentials_most_specific_path_wins verifies the sort is order-independent by constructing the same creds in both orders and asserting the specific one wins. - bridge/auth_manager: check_http_conjunctive_auth_any_missing_required_ raises_gate, check_http_conjunctive_auth_all_required_resolved_is_ready. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * style: cargo fmt after round-3/round-4 review fixes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: untrack accidentally-committed local scratch files Follow-up to50f6771f, which inadvertently staged local-only scratch files via `git add -A`: - .DS_Store - tests_all/ — operator scratch dir with env files (rotate any tokens that were in tests_all/source_env_vars.sh in that commit) - integrations/abound/tests/source_env_vars.sh — same concern - src/cli/snapshots/*.snap.new — stale insta snapshot proposals that should be resolved via `cargo insta review`, not committed This commit removes them from the index and adds matching patterns to .gitignore so they cannot recur. The content is left on disk for local use. NOTE: the tokens that were in the env files are still visible in the50f6771fcommit object and must be rotated regardless of this cleanup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(credentials): address PR #2168 round-5 review - secrets/types: tighten the percent-decoded dot-segment check to also reject segments whose decoded form contains `/` (percent-encoded slash, e.g. `%2f`). Blocks `%2e%2e%2fadmin`-style smuggling on servers that decode encoded slashes before routing (Tomcat with `allowEncodedSlash=true`, older IIS, certain reverse proxies). Literal dots inside a single segment (`foo%2ebar`, `v1%2e2`) stay allowed. (Firat #3126256056) - sandbox/proxy/policy: `find_credential` now returns the most-specific matching mapping via `max_by(...)` on `(match_specificity, secret_name)` rather than `.find(...)`. Behavior matches `SharedCredentialRegistry:: find_for_url` and both WASM `inject_host_credentials` — a host with a global + a `/api/v1/write` credential picks WRITE_TOKEN on writes regardless of Vec order. (Firat #3126256060) - tools/wasm/capabilities_schema: `to_credential_mapping` now returns `Option` and `to_http_capability` filter_maps invalid mappings out entirely. Previously `path_patterns: [""]` survived the warning-only validator and silently widened the credential back to global scope (`path_matches_prefix(path, "")` → true for every absolute path). The drop-on-invalid semantics match the skills pipeline. Removed the duplicate warn-loop in `validate()` since the load path now handles it. (Firat #3126256040) Regression tests: - secrets/types: path_matches_prefix_rejects_percent_encoded_slash_ smuggling covers lowercase/uppercase `%2f` in traversal and embedded positions. - sandbox/proxy/policy: test_sandbox_proxy_most_specific_credential_ wins registers the same credentials in both orders and asserts WRITE_TOKEN always wins on the write path. - tools/wasm/capabilities_schema: three tests covering empty-string pattern (drops mapping), missing-leading-slash (drops mapping), and valid pattern (preserved). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
66 lines
1.4 KiB
Plaintext
66 lines
1.4 KiB
Plaintext
|
|
.env
|
|
.env.local
|
|
.env.*
|
|
!.env.example
|
|
|
|
# macOS metadata
|
|
.DS_Store
|
|
|
|
# Local scratch directories — contain operator-only env files with real
|
|
# production admin/API tokens. Never commit.
|
|
tests_all/
|
|
integrations/abound/tests/source_env_vars.sh
|
|
|
|
# Stale insta snapshot proposals (review via `cargo insta review`)
|
|
*.snap.new
|
|
|
|
# Claude Code worktrees and lock files
|
|
.claude/worktrees/
|
|
.claude/scheduled_tasks.lock
|
|
|
|
# Sidecar tool data
|
|
.sidecar/
|
|
.todos/
|
|
|
|
target/
|
|
|
|
# Python
|
|
__pycache__/
|
|
*.pyc
|
|
/tests/e2e/.venv/
|
|
|
|
# Benchmark results (local runs, not committed)
|
|
bench-results/
|
|
|
|
# Coverage reports (local runs, not committed)
|
|
/coverage/
|
|
|
|
# Canary / E2E run outputs (per-run logs, screenshots, trace artifacts —
|
|
# CI uploads these via actions/upload-artifact; never commit local copies)
|
|
artifacts/
|
|
|
|
# WASM build artifacts (loaded from disk, not bundled)
|
|
*.wasm
|
|
|
|
# Traces
|
|
trace_*.json
|
|
|
|
# Local Claude Code settings (machine-specific, should not be committed)
|
|
.claude/settings.local.json
|
|
.worktrees/
|
|
.ironclaw/
|
|
|
|
# Python cache
|
|
__pycache__/
|
|
*.pyc
|
|
*.pyo
|
|
*.pyd
|
|
engine_trace_*.json
|
|
tests/fixtures/llm_traces/live/github_dev_workflow_full_loop.json
|
|
tests/fixtures/llm_traces/live/github_dev_workflow_full_loop.log
|
|
# Per-test live-replay logs — generated when running `--ignored` live
|
|
# tests locally. Only the .json fixtures for each scenario are checked
|
|
# in; the .log files are local debugging artifacts.
|
|
tests/fixtures/llm_traces/live/*.log
|