code-context-control 2.61.2__py3-none-any.whl → 2.62.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cli/_hook_utils.py +22 -1
- cli/c3.py +135 -5
- cli/commands/parser.py +36 -0
- cli/guide/access.html +288 -0
- cli/guide/bitbucket.html +1 -0
- cli/guide/credentials.html +1 -0
- cli/guide/getting-started.html +1 -0
- cli/guide/index.html +9 -0
- cli/guide/oracle.html +1 -0
- cli/guide/tools.html +1 -0
- cli/guide/workflow.html +1 -0
- cli/hook_access_guard.py +119 -0
- cli/hook_dispatch.py +35 -3
- cli/hook_pretool_enforce.py +14 -2
- cli/hub_server.py +6 -2
- cli/mcp_server.py +7 -3
- cli/server.py +107 -1
- cli/tools/artifacts.py +8 -1
- cli/tools/compress.py +18 -1
- cli/tools/delegate.py +60 -1
- cli/tools/edit.py +11 -0
- cli/tools/filter.py +8 -0
- cli/tools/impact.py +20 -0
- cli/tools/project.py +88 -0
- cli/tools/read.py +12 -0
- cli/tools/search.py +51 -16
- cli/tools/shell.py +131 -0
- cli/tools/status.py +31 -1
- cli/tools/validate.py +13 -0
- cli/ui/app.js +4 -0
- cli/ui/components/access.js +332 -0
- {code_context_control-2.61.2.dist-info → code_context_control-2.62.0.dist-info}/METADATA +40 -1
- {code_context_control-2.61.2.dist-info → code_context_control-2.62.0.dist-info}/RECORD +41 -37
- services/access_guard.py +573 -0
- services/artifact_store.py +19 -0
- services/compressor.py +10 -1
- services/scanner.py +45 -0
- {code_context_control-2.61.2.dist-info → code_context_control-2.62.0.dist-info}/WHEEL +0 -0
- {code_context_control-2.61.2.dist-info → code_context_control-2.62.0.dist-info}/entry_points.txt +0 -0
- {code_context_control-2.61.2.dist-info → code_context_control-2.62.0.dist-info}/licenses/LICENSE +0 -0
- {code_context_control-2.61.2.dist-info → code_context_control-2.62.0.dist-info}/top_level.txt +0 -0
cli/_hook_utils.py
CHANGED
|
@@ -175,6 +175,17 @@ def load_enforcement_state(project_path: Path | None = None, session_id: str = "
|
|
|
175
175
|
# session must not grant unlocks (signal files used to survive /clear).
|
|
176
176
|
if session_id and state.get("session_id") and state["session_id"] != session_id:
|
|
177
177
|
return _empty_state(session_id)
|
|
178
|
+
# Migrate unlock keys written before canonical_key (raw resolve() form):
|
|
179
|
+
# lookups are canonical now, so re-key at load — old and new forms merge.
|
|
180
|
+
migrated: dict = {}
|
|
181
|
+
for k, cats in state.get("unlocked_files", {}).items():
|
|
182
|
+
try:
|
|
183
|
+
key = canonical_key(k)
|
|
184
|
+
except OSError:
|
|
185
|
+
continue
|
|
186
|
+
merged = set(migrated.get(key, [])) | set(cats)
|
|
187
|
+
migrated[key] = sorted(merged)
|
|
188
|
+
state["unlocked_files"] = migrated
|
|
178
189
|
return state
|
|
179
190
|
|
|
180
191
|
|
|
@@ -205,6 +216,16 @@ def record_c3_signal(
|
|
|
205
216
|
save_enforcement_state(state, project_path)
|
|
206
217
|
|
|
207
218
|
|
|
219
|
+
def canonical_key(path) -> str:
|
|
220
|
+
"""Canonical unlock-map key: resolved, forward-slash, casefolded.
|
|
221
|
+
|
|
222
|
+
Must stay form-identical to services.access_guard.canonicalize()'s canon
|
|
223
|
+
string for existing paths (parity-tested) — the unlock map and the
|
|
224
|
+
access evaluator must never disagree about path identity on Windows.
|
|
225
|
+
"""
|
|
226
|
+
return str(Path(path).resolve()).replace("\\", "/").casefold()
|
|
227
|
+
|
|
228
|
+
|
|
208
229
|
def record_unlocked_files(
|
|
209
230
|
paths,
|
|
210
231
|
categories,
|
|
@@ -223,7 +244,7 @@ def record_unlocked_files(
|
|
|
223
244
|
if not fp:
|
|
224
245
|
continue
|
|
225
246
|
try:
|
|
226
|
-
normalized =
|
|
247
|
+
normalized = canonical_key(fp)
|
|
227
248
|
except OSError:
|
|
228
249
|
continue
|
|
229
250
|
cats = set(state["unlocked_files"].get(normalized, []))
|
cli/c3.py
CHANGED
|
@@ -92,7 +92,17 @@ console = Console() if HAS_RICH else None
|
|
|
92
92
|
# Config
|
|
93
93
|
CONFIG_DIR = ".c3"
|
|
94
94
|
CONFIG_FILE = ".c3/config.json"
|
|
95
|
-
__version__ = "2.
|
|
95
|
+
__version__ = "2.62.0"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _compress_file_cli(compressor, path, mode="smart", **kw):
|
|
99
|
+
"""compress_file for human-CLI paths: AccessDenied → error dict, not a
|
|
100
|
+
traceback. The refusal text names the rule so the operator can act."""
|
|
101
|
+
from services import access_guard as _ag
|
|
102
|
+
try:
|
|
103
|
+
return compressor.compress_file(path, mode, **kw)
|
|
104
|
+
except _ag.AccessDenied as exc:
|
|
105
|
+
return {"error": exc.message}
|
|
96
106
|
|
|
97
107
|
|
|
98
108
|
def _command_deps() -> CommandDeps:
|
|
@@ -1338,7 +1348,7 @@ def _benchmark_extract_preview(full_path: Path, compressor: CodeCompressor, patt
|
|
|
1338
1348
|
extracted = ""
|
|
1339
1349
|
|
|
1340
1350
|
if ext in code_exts and not pattern:
|
|
1341
|
-
result = compressor
|
|
1351
|
+
result = _compress_file_cli(compressor, str(full_path), "smart")
|
|
1342
1352
|
extracted = result.get("compressed", "") if "error" not in result else f"Error: {result['error']}"
|
|
1343
1353
|
|
|
1344
1354
|
elif ext == ".jsonl" and not pattern:
|
|
@@ -1617,7 +1627,7 @@ def _benchmark_delegate_optional(project_path: Path, sample: list[tuple[Path, st
|
|
|
1617
1627
|
"Focus on responsibilities, important functions/classes, and notable dependencies."
|
|
1618
1628
|
)
|
|
1619
1629
|
|
|
1620
|
-
compressed_result = compressor
|
|
1630
|
+
compressed_result = _compress_file_cli(compressor, str(fpath), "smart")
|
|
1621
1631
|
compressed_context = compressed_result.get("compressed", "") if isinstance(compressed_result, dict) else ""
|
|
1622
1632
|
if not compressed_context:
|
|
1623
1633
|
compressed_context = raw_content
|
|
@@ -3478,7 +3488,7 @@ def cmd_benchmark(args):
|
|
|
3478
3488
|
raw_content = content
|
|
3479
3489
|
comp_baseline_latencies.append((time.perf_counter() - t_read) * 1000)
|
|
3480
3490
|
t0 = time.perf_counter()
|
|
3481
|
-
result = compressor
|
|
3491
|
+
result = _compress_file_cli(compressor, str(fpath), "smart")
|
|
3482
3492
|
comp_c3_latencies.append((time.perf_counter() - t0) * 1000)
|
|
3483
3493
|
raw_tokens = count_tokens(raw_content)
|
|
3484
3494
|
comp_orig += raw_tokens
|
|
@@ -3544,7 +3554,7 @@ def cmd_benchmark(args):
|
|
|
3544
3554
|
tmp.write(raw_extracted)
|
|
3545
3555
|
tmp_path = tmp.name
|
|
3546
3556
|
try:
|
|
3547
|
-
comp_res = compressor
|
|
3557
|
+
comp_res = _compress_file_cli(compressor, tmp_path, mode="smart")
|
|
3548
3558
|
extracted_text += comp_res.get("compressed", raw_extracted)
|
|
3549
3559
|
finally:
|
|
3550
3560
|
if os.path.exists(tmp_path):
|
|
@@ -5203,6 +5213,9 @@ def cmd_install_mcp(args):
|
|
|
5203
5213
|
]
|
|
5204
5214
|
|
|
5205
5215
|
# ── PreToolUse hooks (enforcement — blocks native tools without prior c3_*) ──
|
|
5216
|
+
# Bash/run_shell_command joined in v2.62: the Access Guard sub-hook
|
|
5217
|
+
# scans shell commands (best-effort); without these matchers PreToolUse
|
|
5218
|
+
# simply never fires for shell — proven bypass, see docs/access-guard.md.
|
|
5206
5219
|
_pre_matcher_names = [
|
|
5207
5220
|
read_matcher,
|
|
5208
5221
|
grep_matcher,
|
|
@@ -5210,6 +5223,8 @@ def cmd_install_mcp(args):
|
|
|
5210
5223
|
edit_matcher,
|
|
5211
5224
|
write_matcher,
|
|
5212
5225
|
*extra_edit_matchers,
|
|
5226
|
+
shell_matcher,
|
|
5227
|
+
"run_shell_command",
|
|
5213
5228
|
]
|
|
5214
5229
|
desired_pre_hooks = [
|
|
5215
5230
|
{"matcher": m, "hooks": [{"type": "command", "command": hook_pretool_cmd}]}
|
|
@@ -5899,6 +5914,120 @@ def _creds_cmd_import(args, project_path: str) -> None:
|
|
|
5899
5914
|
"(use --overwrite to replace)")
|
|
5900
5915
|
|
|
5901
5916
|
|
|
5917
|
+
def cmd_access(args):
|
|
5918
|
+
"""Access Guard rule management — human-only mutation surface (spec §1)."""
|
|
5919
|
+
sub = getattr(args, "access_cmd", None)
|
|
5920
|
+
if not sub:
|
|
5921
|
+
print("Usage: c3 access {list,add,remove,check} [args]")
|
|
5922
|
+
return
|
|
5923
|
+
|
|
5924
|
+
project_path = getattr(args, "project_path", ".") or "."
|
|
5925
|
+
|
|
5926
|
+
if sub == "list":
|
|
5927
|
+
_access_cmd_list(args, project_path)
|
|
5928
|
+
elif sub == "add":
|
|
5929
|
+
_access_cmd_add(args, project_path)
|
|
5930
|
+
elif sub == "remove":
|
|
5931
|
+
_access_cmd_remove(args, project_path)
|
|
5932
|
+
elif sub == "check":
|
|
5933
|
+
_access_cmd_check(args, project_path)
|
|
5934
|
+
else:
|
|
5935
|
+
print(f"Unknown access subcommand: {sub}")
|
|
5936
|
+
|
|
5937
|
+
|
|
5938
|
+
def _access_scope(args) -> str:
|
|
5939
|
+
return "global" if getattr(args, "use_global", False) else "project"
|
|
5940
|
+
|
|
5941
|
+
|
|
5942
|
+
def _access_audit(action: str, glob: str, kind: str, scope: str,
|
|
5943
|
+
project_path: str) -> None:
|
|
5944
|
+
"""Ledger + activity log for CLI rule mutations. Identifiers only; failure-safe."""
|
|
5945
|
+
try:
|
|
5946
|
+
from services.activity_log import ActivityLog
|
|
5947
|
+
ActivityLog(project_path).log("access_action", {
|
|
5948
|
+
"kind": "access", "action": action, "glob": glob,
|
|
5949
|
+
"rule_kind": kind, "scope": scope, "via": "cli",
|
|
5950
|
+
})
|
|
5951
|
+
except Exception:
|
|
5952
|
+
pass
|
|
5953
|
+
try:
|
|
5954
|
+
from services.edit_ledger import EditLedger
|
|
5955
|
+
EditLedger(project_path).log_edit(
|
|
5956
|
+
file=f"access://{glob}", change_type=f"access_{action}",
|
|
5957
|
+
summary=f"{action} {kind} rule '{glob}' ({scope}) via `c3 access`",
|
|
5958
|
+
tags=["access", action],
|
|
5959
|
+
detail={"kind": "access", "action": action, "glob": glob,
|
|
5960
|
+
"rule_kind": kind, "scope": scope},
|
|
5961
|
+
)
|
|
5962
|
+
except Exception:
|
|
5963
|
+
pass
|
|
5964
|
+
|
|
5965
|
+
|
|
5966
|
+
def _access_cmd_list(args, project_path: str) -> None:
|
|
5967
|
+
from services import access_guard
|
|
5968
|
+
|
|
5969
|
+
scopes = access_guard.list_rules(project_path)
|
|
5970
|
+
notes = {"builtin": "built-in, always on",
|
|
5971
|
+
"global": "~/.c3/config.json",
|
|
5972
|
+
"project": ".c3/config.json"}
|
|
5973
|
+
for scope in ("builtin", "global", "project"):
|
|
5974
|
+
sec = scopes.get(scope) or {}
|
|
5975
|
+
rules = [(k, g) for k in ("deny", "read_only") for g in sec.get(k, [])]
|
|
5976
|
+
print(f"[{scope}] ({notes[scope]}) — {len(rules)} rule(s)")
|
|
5977
|
+
for kind, glob in rules:
|
|
5978
|
+
print(f" {kind:<10} {glob}")
|
|
5979
|
+
if sec.get("corrupt"):
|
|
5980
|
+
print(" [warn] access section invalid — scope fails closed "
|
|
5981
|
+
"(deny-all); fix config.json 'access' by hand")
|
|
5982
|
+
print()
|
|
5983
|
+
print(access_guard.COVERAGE_MATRIX)
|
|
5984
|
+
|
|
5985
|
+
|
|
5986
|
+
def _access_cmd_add(args, project_path: str) -> None:
|
|
5987
|
+
from services import access_guard
|
|
5988
|
+
|
|
5989
|
+
scope = _access_scope(args)
|
|
5990
|
+
try:
|
|
5991
|
+
result = access_guard.set_rule(args.glob, args.kind, scope, project_path)
|
|
5992
|
+
except ValueError as exc:
|
|
5993
|
+
print(f"[error] {exc}")
|
|
5994
|
+
return
|
|
5995
|
+
if result["added"]:
|
|
5996
|
+
_access_audit("add", result["glob"], args.kind, scope, project_path)
|
|
5997
|
+
print(f"[OK] Added {args.kind} rule '{result['glob']}' (scope={scope})")
|
|
5998
|
+
else:
|
|
5999
|
+
print(f"[=] Rule already present: '{result['glob']}' "
|
|
6000
|
+
f"({args.kind}, {scope})")
|
|
6001
|
+
|
|
6002
|
+
|
|
6003
|
+
def _access_cmd_remove(args, project_path: str) -> None:
|
|
6004
|
+
from services import access_guard
|
|
6005
|
+
|
|
6006
|
+
scope = _access_scope(args)
|
|
6007
|
+
try:
|
|
6008
|
+
result = access_guard.remove_rule(args.glob, args.kind, scope, project_path)
|
|
6009
|
+
except ValueError as exc:
|
|
6010
|
+
print(f"[error] {exc}")
|
|
6011
|
+
return
|
|
6012
|
+
if result["removed"]:
|
|
6013
|
+
_access_audit("remove", result["glob"], args.kind, scope, project_path)
|
|
6014
|
+
print(f"[OK] Removed {args.kind} rule '{result['glob']}' (scope={scope})")
|
|
6015
|
+
else:
|
|
6016
|
+
print(f"[error] no {args.kind} rule matching '{result['glob']}' "
|
|
6017
|
+
f"in {scope} scope")
|
|
6018
|
+
|
|
6019
|
+
|
|
6020
|
+
def _access_cmd_check(args, project_path: str) -> None:
|
|
6021
|
+
from services import access_guard
|
|
6022
|
+
|
|
6023
|
+
op = getattr(args, "op", "read") or "read"
|
|
6024
|
+
denial = access_guard.check(args.target, op, project_path)
|
|
6025
|
+
if denial is None:
|
|
6026
|
+
print(f"[OK] {op} allowed: {args.target}")
|
|
6027
|
+
else:
|
|
6028
|
+
print(access_guard.refusal(denial, args.target, op))
|
|
6029
|
+
|
|
6030
|
+
|
|
5902
6031
|
def cmd_jira(args):
|
|
5903
6032
|
"""Jira Cloud / Data Center credential + workspace management."""
|
|
5904
6033
|
sub = getattr(args, "jira_cmd", None)
|
|
@@ -7199,6 +7328,7 @@ def main():
|
|
|
7199
7328
|
"bitbucket": cmd_bitbucket,
|
|
7200
7329
|
"jira": cmd_jira,
|
|
7201
7330
|
"creds": cmd_creds,
|
|
7331
|
+
"access": cmd_access,
|
|
7202
7332
|
"oracle": cmd_oracle,
|
|
7203
7333
|
"upgrade": cmd_upgrade,
|
|
7204
7334
|
}
|
cli/commands/parser.py
CHANGED
|
@@ -447,6 +447,42 @@ def build_parser(version: str, parse_cli_ide_arg):
|
|
|
447
447
|
cr_import.add_argument("--overwrite", action="store_true", help="Replace entries already registered in the target scope")
|
|
448
448
|
cr_import.add_argument("--path", dest="project_path", default=".", help="Project directory (default: current)")
|
|
449
449
|
|
|
450
|
+
# ── Access Guard (v2.62.0) ──────────────────────────────────────────
|
|
451
|
+
# Human-only mutation surface (frozen spec docs/access-guard.md §1):
|
|
452
|
+
# rule changes happen here or in the UI tab, never via an agent tool.
|
|
453
|
+
p_access = subparsers.add_parser(
|
|
454
|
+
"access",
|
|
455
|
+
help="Access Guard — path deny/read-only rules the agent must respect",
|
|
456
|
+
)
|
|
457
|
+
access_subs = p_access.add_subparsers(dest="access_cmd")
|
|
458
|
+
|
|
459
|
+
ac_list = access_subs.add_parser(
|
|
460
|
+
"list", help="Show rules per scope (builtin + global + project) and coverage"
|
|
461
|
+
)
|
|
462
|
+
ac_list.add_argument("--path", dest="project_path", default=".", help="Project directory (default: current)")
|
|
463
|
+
|
|
464
|
+
ac_add = access_subs.add_parser("add", help="Add a rule to the project (or --global) scope")
|
|
465
|
+
ac_add.add_argument("glob", help="POSIX glob (** crosses directories), e.g. 'secrets/**' or '*.pem'")
|
|
466
|
+
ac_add.add_argument("--kind", choices=["deny", "read_only"], required=True,
|
|
467
|
+
help="deny = no read/write/enumerate; read_only = no write")
|
|
468
|
+
ac_add.add_argument("--global", dest="use_global", action="store_true",
|
|
469
|
+
help="Store in the global scope (~/.c3) so every C3 project enforces it")
|
|
470
|
+
ac_add.add_argument("--path", dest="project_path", default=".", help="Project directory (default: current)")
|
|
471
|
+
|
|
472
|
+
ac_remove = access_subs.add_parser("remove", help="Remove a rule from the project (or --global) scope")
|
|
473
|
+
ac_remove.add_argument("glob", help="The stored glob to remove (case-insensitive match)")
|
|
474
|
+
ac_remove.add_argument("--kind", choices=["deny", "read_only"], required=True,
|
|
475
|
+
help="Which rule list the glob lives in")
|
|
476
|
+
ac_remove.add_argument("--global", dest="use_global", action="store_true",
|
|
477
|
+
help="Remove from the global scope (~/.c3)")
|
|
478
|
+
ac_remove.add_argument("--path", dest="project_path", default=".", help="Project directory (default: current)")
|
|
479
|
+
|
|
480
|
+
ac_check = access_subs.add_parser("check", help="Test a path: verdict + matched rule + refusal string")
|
|
481
|
+
ac_check.add_argument("target", help="Path to test (absolute or project-relative)")
|
|
482
|
+
ac_check.add_argument("--op", choices=["read", "write"], default="read",
|
|
483
|
+
help="Operation to evaluate (default: read)")
|
|
484
|
+
ac_check.add_argument("--path", dest="project_path", default=".", help="Project directory (default: current)")
|
|
485
|
+
|
|
450
486
|
# ── Oracle Discovery API (v2.32.0) ──────────────────────────────────
|
|
451
487
|
p_oracle = subparsers.add_parser(
|
|
452
488
|
"oracle",
|
cli/guide/access.html
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Access Guard — C3 Guide</title>
|
|
7
|
+
<link rel="stylesheet" href="shared.css">
|
|
8
|
+
<style>
|
|
9
|
+
.pill-row { display: flex; gap: 8px; flex-wrap: wrap; margin: 16px 0 28px; }
|
|
10
|
+
.pill {
|
|
11
|
+
padding: 4px 12px; border-radius: 999px; font-size: 11px;
|
|
12
|
+
font-family: var(--mono); border: 1px solid var(--border);
|
|
13
|
+
color: var(--text-dim); background: var(--bg-3);
|
|
14
|
+
}
|
|
15
|
+
.pill-accent { color: var(--cyan); border-color: var(--cyan); }
|
|
16
|
+
.pill-green { color: var(--green); border-color: var(--green); }
|
|
17
|
+
.pill-amber { color: var(--amber); border-color: var(--amber); }
|
|
18
|
+
.pill-red { color: var(--red); border-color: var(--red); }
|
|
19
|
+
|
|
20
|
+
.callout {
|
|
21
|
+
border-left: 3px solid var(--cyan);
|
|
22
|
+
background: var(--bg-2);
|
|
23
|
+
padding: 12px 16px;
|
|
24
|
+
border-radius: 0 var(--radius) var(--radius) 0;
|
|
25
|
+
margin: 16px 0;
|
|
26
|
+
}
|
|
27
|
+
.callout-warn { border-color: var(--amber); }
|
|
28
|
+
.callout-danger { border-color: var(--red); }
|
|
29
|
+
.callout-good { border-color: var(--green); }
|
|
30
|
+
.callout p:last-child { margin-bottom: 0; }
|
|
31
|
+
.callout strong { color: var(--text); }
|
|
32
|
+
|
|
33
|
+
.rule-table th, .rule-table td { padding: 8px 12px; }
|
|
34
|
+
.rule-table tr:hover { background: var(--bg-3); }
|
|
35
|
+
.rule-table .k-deny { color: var(--red); font-family: var(--mono); }
|
|
36
|
+
.rule-table .k-ro { color: var(--amber); font-family: var(--mono); }
|
|
37
|
+
.rule-table .k-tag { font-family: var(--mono); font-size: 12px; }
|
|
38
|
+
|
|
39
|
+
.step-grid {
|
|
40
|
+
display: grid; gap: 12px;
|
|
41
|
+
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
42
|
+
margin: 16px 0;
|
|
43
|
+
}
|
|
44
|
+
.step-card {
|
|
45
|
+
padding: 16px;
|
|
46
|
+
background: var(--bg-2);
|
|
47
|
+
border: 1px solid var(--border);
|
|
48
|
+
border-radius: var(--radius);
|
|
49
|
+
}
|
|
50
|
+
.step-card .step-num {
|
|
51
|
+
display: inline-block; width: 22px; height: 22px;
|
|
52
|
+
background: var(--cyan); color: #000;
|
|
53
|
+
border-radius: 50%;
|
|
54
|
+
text-align: center; line-height: 22px;
|
|
55
|
+
font-size: 11px; font-weight: 700;
|
|
56
|
+
margin-right: 8px;
|
|
57
|
+
}
|
|
58
|
+
.step-card h4 { display: inline; font-size: 14px; }
|
|
59
|
+
</style>
|
|
60
|
+
</head>
|
|
61
|
+
<body>
|
|
62
|
+
|
|
63
|
+
<div class="layout">
|
|
64
|
+
<!-- Header -->
|
|
65
|
+
<header class="header">
|
|
66
|
+
<a href="index.html" class="header-logo">
|
|
67
|
+
<span class="logo-badge">C3</span>
|
|
68
|
+
Claude Code Companion
|
|
69
|
+
</a>
|
|
70
|
+
<nav class="header-nav">
|
|
71
|
+
<a href="index.html">Home</a>
|
|
72
|
+
<a href="getting-started.html">Get Started</a>
|
|
73
|
+
<a href="workflow.html">Workflow</a>
|
|
74
|
+
<a href="tools.html">Tools</a>
|
|
75
|
+
<a href="credentials.html">Credentials</a>
|
|
76
|
+
<a href="access.html" class="active">Access</a>
|
|
77
|
+
<a href="bitbucket.html">Bitbucket</a>
|
|
78
|
+
<a href="oracle.html">Oracle</a>
|
|
79
|
+
<a href="https://github.com/sponsors/drknowhow" target="_blank" rel="noopener" style="color:#EA4AAA">♥ Sponsor</a>
|
|
80
|
+
</nav>
|
|
81
|
+
<span class="header-version">v2.62.0</span>
|
|
82
|
+
</header>
|
|
83
|
+
|
|
84
|
+
<!-- Sidebar -->
|
|
85
|
+
<aside class="sidebar">
|
|
86
|
+
<div class="sidebar-section">
|
|
87
|
+
<div class="sidebar-label">On this page</div>
|
|
88
|
+
<a href="#overview" class="sidebar-link"><span class="icon">🛡️</span> Overview</a>
|
|
89
|
+
<a href="#quick-start" class="sidebar-link"><span class="icon">🚀</span> Quick start</a>
|
|
90
|
+
<a href="#rules" class="sidebar-link"><span class="icon">📐</span> Rules & semantics</a>
|
|
91
|
+
<a href="#builtins" class="sidebar-link"><span class="icon">🔒</span> Built-in protections</a>
|
|
92
|
+
<a href="#refusals" class="sidebar-link"><span class="icon">✋</span> Refusal messages</a>
|
|
93
|
+
<a href="#coverage" class="sidebar-link"><span class="icon">🗺️</span> Coverage — honest limits</a>
|
|
94
|
+
<a href="#cli" class="sidebar-link"><span class="icon">💻</span> CLI commands</a>
|
|
95
|
+
<a href="#ui" class="sidebar-link"><span class="icon">🖥️</span> UI tab & probe</a>
|
|
96
|
+
<a href="#troubleshooting" class="sidebar-link"><span class="icon">🛠️</span> Troubleshooting</a>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="sidebar-section">
|
|
99
|
+
<div class="sidebar-label">Related</div>
|
|
100
|
+
<a href="credentials.html" class="sidebar-link">Credential Vault</a>
|
|
101
|
+
<a href="workflow.html" class="sidebar-link">Standard workflow</a>
|
|
102
|
+
<a href="getting-started.html" class="sidebar-link">First-time setup</a>
|
|
103
|
+
</div>
|
|
104
|
+
</aside>
|
|
105
|
+
|
|
106
|
+
<!-- Main -->
|
|
107
|
+
<main class="main">
|
|
108
|
+
<h1 id="overview">Access Guard</h1>
|
|
109
|
+
<p class="lead">
|
|
110
|
+
Mark the paths an AI agent must never read, or never write — and have
|
|
111
|
+
every C3 surface enforce it. Two glob lists in your project (or global)
|
|
112
|
+
config, one evaluator, refusals that tell the agent exactly why and
|
|
113
|
+
what to do instead.
|
|
114
|
+
</p>
|
|
115
|
+
|
|
116
|
+
<div class="pill-row">
|
|
117
|
+
<span class="pill pill-red">deny — no read, no write</span>
|
|
118
|
+
<span class="pill pill-amber">read_only — no write</span>
|
|
119
|
+
<span class="pill pill-green">tighten-only</span>
|
|
120
|
+
<span class="pill pill-accent">v2.62.0+</span>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div class="callout callout-warn">
|
|
124
|
+
<p><strong>What this is — and is not.</strong> Access Guard protects a
|
|
125
|
+
<em>cooperative</em> agent against mistakes and prompt-injection: an
|
|
126
|
+
agent that names a protected path gets a firm, reasoned refusal at
|
|
127
|
+
every C3 surface. It is <strong>not a sandbox</strong> — it governs
|
|
128
|
+
file actions invoked through C3's tools and hooks, but does not
|
|
129
|
+
isolate raw OS processes or external editors. See
|
|
130
|
+
<a href="#coverage">Coverage</a> for exactly where each layer
|
|
131
|
+
holds.</p>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<h2 id="quick-start">Quick start</h2>
|
|
135
|
+
<div class="step-grid">
|
|
136
|
+
<div class="step-card">
|
|
137
|
+
<span class="step-num">1</span><h4>Add a rule</h4>
|
|
138
|
+
<p>UI → project → <strong>Access Guard</strong> tab → Add rule, or:</p>
|
|
139
|
+
<pre><code>c3 access add "secrets/**" --kind deny</code></pre>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="step-card">
|
|
142
|
+
<span class="step-num">2</span><h4>Test a path</h4>
|
|
143
|
+
<p>Use the tab's <strong>Test path</strong> probe, or:</p>
|
|
144
|
+
<pre><code>c3 access check secrets/key.txt</code></pre>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="step-card">
|
|
147
|
+
<span class="step-num">3</span><h4>Let it work</h4>
|
|
148
|
+
<p>Agent attempts on protected paths return
|
|
149
|
+
<code>[c3-access:denied]</code> refusals that name the rule and tell
|
|
150
|
+
the agent not to retry.</p>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<h2 id="rules">Rules & semantics</h2>
|
|
155
|
+
<p>Rules live in the <code>access</code> section of
|
|
156
|
+
<code>.c3/config.json</code> (project scope) and
|
|
157
|
+
<code>~/.c3/config.json</code> (global scope):</p>
|
|
158
|
+
<pre><code>{
|
|
159
|
+
"access": {
|
|
160
|
+
"deny": ["secrets/**", "*.pem"],
|
|
161
|
+
"read_only": ["docs/legal/**", "migrations/**"]
|
|
162
|
+
}
|
|
163
|
+
}</code></pre>
|
|
164
|
+
<table class="rule-table">
|
|
165
|
+
<tr><th>Rule kind</th><th>Read</th><th>Write / create / delete</th><th>Appears in search?</th></tr>
|
|
166
|
+
<tr><td class="k-deny">deny</td><td>✗</td><td>✗</td><td>✗ (never listed or hinted)</td></tr>
|
|
167
|
+
<tr><td class="k-ro">read_only</td><td>✓ (unless another rule denies)</td><td>✗</td><td>✓</td></tr>
|
|
168
|
+
</table>
|
|
169
|
+
<ul>
|
|
170
|
+
<li><strong>Tighten-only.</strong> There is no <code>allow</code> list.
|
|
171
|
+
Scopes merge as a union — a rule anywhere applies everywhere. A
|
|
172
|
+
project config can only add protection, never grant access; an
|
|
173
|
+
unknown key in the section is a hard config error.</li>
|
|
174
|
+
<li><strong>Globs are POSIX-style</strong>, case-insensitive.
|
|
175
|
+
<code>**</code> crosses directories, <code>*</code> does not; a
|
|
176
|
+
pattern without <code>/</code> matches the file name at any depth
|
|
177
|
+
(<code>*.pem</code> matches <code>a/b/server.pem</code>).</li>
|
|
178
|
+
<li><strong>deny also means deny-create.</strong> Path manipulation
|
|
179
|
+
and OS aliases (short names, trailing dots/spaces, stream syntax)
|
|
180
|
+
are normalized before matching, so alternate spellings of a
|
|
181
|
+
protected name cannot slip past the rule.</li>
|
|
182
|
+
<li><strong>deny also means deny-enumerate.</strong> Search, maps, and
|
|
183
|
+
listings never reveal a denied file's existence. When any rules are
|
|
184
|
+
active, search output carries a <code>[c3-access:limited]</code>
|
|
185
|
+
footer so the agent doesn't conclude "missing" means "doesn't
|
|
186
|
+
exist".</li>
|
|
187
|
+
<li><strong>Rule text is not secret.</strong> Refusals and
|
|
188
|
+
<code>c3_status</code> show the matched glob — the guard protects
|
|
189
|
+
file contents and existence, not the rule list. Name globs
|
|
190
|
+
accordingly.</li>
|
|
191
|
+
<li><strong>All rule changes are human-only</strong> — UI or CLI, and
|
|
192
|
+
every mutation is logged to the project's edit ledger. Agents have
|
|
193
|
+
no tool that can add, remove, or loosen rules.</li>
|
|
194
|
+
<li><strong>Local-only in v1.</strong> <code>.c3/</code> is gitignored:
|
|
195
|
+
rules do not travel with a cloned repo.</li>
|
|
196
|
+
</ul>
|
|
197
|
+
|
|
198
|
+
<h2 id="builtins">Built-in protections (always on)</h2>
|
|
199
|
+
<p>These apply in every C3 project, with no configuration, and cannot be
|
|
200
|
+
overridden. If the access config is corrupt or unparseable, that scope
|
|
201
|
+
fails <em>closed</em> — everything in it is denied until fixed.</p>
|
|
202
|
+
<table class="rule-table">
|
|
203
|
+
<tr><th>Paths</th><th>Protection</th><th>Why</th></tr>
|
|
204
|
+
<tr><td class="k-tag">**/.env*</td><td class="k-deny">deny</td><td>Secrets never enter agent context</td></tr>
|
|
205
|
+
<tr><td class="k-tag">.c3/secrets.enc · .c3/cred_state.json</td><td class="k-deny">deny</td><td>Credential vault sidecars</td></tr>
|
|
206
|
+
<tr><td class="k-tag">.c3/** · ~/.c3/**</td><td class="k-ro">write-deny</td><td>Config self-tampering (incl. access rules + credential registry)</td></tr>
|
|
207
|
+
<tr><td class="k-tag">.claude/settings*.json</td><td class="k-ro">write-deny</td><td>Hook/permission self-modification</td></tr>
|
|
208
|
+
<tr><td class="k-tag">.git/**</td><td class="k-ro">write-deny</td><td>git hooks are a code-execution vector (reads stay open)</td></tr>
|
|
209
|
+
<tr><td class="k-tag">C3 install directory</td><td class="k-ro">write-deny</td><td>Agent self-modification (installed layouts only)</td></tr>
|
|
210
|
+
<tr><td class="k-tag">*.pem · id_rsa* · *.key</td><td class="k-deny">default rule</td><td>Seeded in global scope — visible and removable, unlike builtins</td></tr>
|
|
211
|
+
</table>
|
|
212
|
+
|
|
213
|
+
<h2 id="refusals">Refusal messages</h2>
|
|
214
|
+
<p>Every denial carries a stable machine tag, the matched rule and scope,
|
|
215
|
+
and explicit no-retry guidance — so the agent pivots instead of
|
|
216
|
+
retry-looping or silently recreating "missing" files.</p>
|
|
217
|
+
<table class="rule-table">
|
|
218
|
+
<tr><th>Tag</th><th>Meaning</th></tr>
|
|
219
|
+
<tr><td class="k-tag">[c3-access:denied]</td><td>Operation refused by a deny rule (or a write refused where read_only applies via hooks). Policy, not an error — the agent is told not to retry or route around it.</td></tr>
|
|
220
|
+
<tr><td class="k-tag">[c3-access:read_only]</td><td>Write refused; reads remain separately evaluated.</td></tr>
|
|
221
|
+
<tr><td class="k-tag">[c3-access:limited]</td><td>Search/listing footer: results may omit protected paths — a file that doesn't appear may still exist.</td></tr>
|
|
222
|
+
<tr><td class="k-tag">[c3-access:error]</td><td>The enforcement hook itself failed — native write tools are denied until it loads (fail-closed). See <a href="#troubleshooting">Troubleshooting</a>.</td></tr>
|
|
223
|
+
</table>
|
|
224
|
+
|
|
225
|
+
<h2 id="coverage">Coverage — honest limits</h2>
|
|
226
|
+
<div class="callout callout-good">
|
|
227
|
+
<p><strong>Enforced:</strong> C3 MCP tools (any agent that uses C3 —
|
|
228
|
+
Claude Code, Codex, Gemini, Oracle-connected models) · Claude Code
|
|
229
|
+
native tools (PreToolUse hooks, fail-closed) · <code>c3_shell</code>
|
|
230
|
+
(best-effort command scan, advisory).</p>
|
|
231
|
+
</div>
|
|
232
|
+
<div class="callout callout-danger">
|
|
233
|
+
<p><strong>Not enforced:</strong> a non-Claude agent's raw shell or
|
|
234
|
+
direct file APIs — including native file tools in non-Claude CLI
|
|
235
|
+
environments unless they route through C3 tools — external editors,
|
|
236
|
+
and OS-level access. Renames/moves via shell commands are a known
|
|
237
|
+
v1 gap. Access Guard is a guardrail
|
|
238
|
+
for cooperative agents — pair it with OS permissions when you need a
|
|
239
|
+
real boundary.</p>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<h2 id="cli">CLI commands</h2>
|
|
243
|
+
<pre><code>c3 access list # rules by scope, builtins included
|
|
244
|
+
c3 access add "secrets/**" --kind deny # project scope
|
|
245
|
+
c3 access add "*.pem" --kind deny --global # global scope
|
|
246
|
+
c3 access remove "secrets/**" --kind deny
|
|
247
|
+
c3 access check secrets/key.txt # probe: verdict + matched rule
|
|
248
|
+
c3 access check docs/x.md --op write</code></pre>
|
|
249
|
+
|
|
250
|
+
<h2 id="ui">UI tab & probe</h2>
|
|
251
|
+
<p>Each project's UI gains an <strong>Access Guard</strong> tab: rules
|
|
252
|
+
grouped by scope (builtins shown locked), an add-rule form, delete with
|
|
253
|
+
typed confirmation for deny rules, and a <strong>Test path</strong>
|
|
254
|
+
probe that shows the verdict, matched rule, and the exact refusal the
|
|
255
|
+
agent would see. The coverage matrix above is pinned at the bottom of
|
|
256
|
+
the tab.</p>
|
|
257
|
+
|
|
258
|
+
<h2 id="troubleshooting">Troubleshooting</h2>
|
|
259
|
+
<table class="kv-table rule-table">
|
|
260
|
+
<tr><td class="k-tag">[c3-access:error] on every write</td>
|
|
261
|
+
<td>The access hook failed to load — this is fail-closed by design.
|
|
262
|
+
Check <code>.c3/hook_errors.log</code>, rerun
|
|
263
|
+
<code>c3 install-mcp</code>. C3 MCP tools keep working
|
|
264
|
+
meanwhile.</td></tr>
|
|
265
|
+
<tr><td class="k-tag">Everything in a scope denied</td>
|
|
266
|
+
<td>That scope's <code>access</code> section is corrupt or has an
|
|
267
|
+
unknown key. Fix the JSON; <code>c3 access list</code> names the
|
|
268
|
+
scope.</td></tr>
|
|
269
|
+
<tr><td class="k-tag">Expected file "missing" in search</td>
|
|
270
|
+
<td>Check <code>c3 access check <path></code> — deny rules
|
|
271
|
+
remove files from search entirely (deny-enumerate).</td></tr>
|
|
272
|
+
<tr><td class="k-tag">Rule not matching</td>
|
|
273
|
+
<td>Globs are POSIX-style: use forward slashes; remember
|
|
274
|
+
<code>*</code> does not cross directories — use <code>**</code>.
|
|
275
|
+
Probe with <code>c3 access check</code>.</td></tr>
|
|
276
|
+
</table>
|
|
277
|
+
|
|
278
|
+
<div class="callout">
|
|
279
|
+
<p><strong>Relationship to the Credential Vault.</strong> Credentials
|
|
280
|
+
protect <em>values</em> (never in agent context, injected at the
|
|
281
|
+
subprocess boundary); Access Guard protects <em>files and folders</em>.
|
|
282
|
+
The vault's registry and sidecars are covered by Access Guard builtins,
|
|
283
|
+
and both systems log every mutation to the same edit ledger.</p>
|
|
284
|
+
</div>
|
|
285
|
+
</main>
|
|
286
|
+
</div>
|
|
287
|
+
</body>
|
|
288
|
+
</html>
|
cli/guide/bitbucket.html
CHANGED
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
<a href="workflow.html">Workflow</a>
|
|
79
79
|
<a href="tools.html">Tools</a>
|
|
80
80
|
<a href="credentials.html">Credentials</a>
|
|
81
|
+
<a href="access.html">Access</a>
|
|
81
82
|
<a href="bitbucket.html" class="active">Bitbucket</a>
|
|
82
83
|
<a href="oracle.html">Oracle</a>
|
|
83
84
|
<a href="https://github.com/sponsors/drknowhow" target="_blank" rel="noopener" style="color:#EA4AAA">♥ Sponsor</a>
|
cli/guide/credentials.html
CHANGED
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
<a href="workflow.html">Workflow</a>
|
|
88
88
|
<a href="tools.html">Tools</a>
|
|
89
89
|
<a href="credentials.html" class="active">Credentials</a>
|
|
90
|
+
<a href="access.html">Access</a>
|
|
90
91
|
<a href="bitbucket.html">Bitbucket</a>
|
|
91
92
|
<a href="oracle.html">Oracle</a>
|
|
92
93
|
<a href="https://github.com/sponsors/drknowhow" target="_blank" rel="noopener" style="color:#EA4AAA">♥ Sponsor</a>
|
cli/guide/getting-started.html
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
<a href="workflow.html">Workflow</a>
|
|
22
22
|
<a href="tools.html">Tools</a>
|
|
23
23
|
<a href="credentials.html">Credentials</a>
|
|
24
|
+
<a href="access.html">Access</a>
|
|
24
25
|
<a href="bitbucket.html">Bitbucket</a>
|
|
25
26
|
<a href="oracle.html">Oracle</a>
|
|
26
27
|
<a href="https://github.com/sponsors/drknowhow" target="_blank" rel="noopener" style="color:#EA4AAA">♥ Sponsor</a>
|
cli/guide/index.html
CHANGED
|
@@ -223,6 +223,7 @@
|
|
|
223
223
|
<a href="workflow.html">Workflow</a>
|
|
224
224
|
<a href="tools.html">Tools</a>
|
|
225
225
|
<a href="credentials.html">Credentials</a>
|
|
226
|
+
<a href="access.html">Access</a>
|
|
226
227
|
<a href="bitbucket.html">Bitbucket</a>
|
|
227
228
|
<a href="oracle.html">Oracle</a>
|
|
228
229
|
<a href="https://github.com/sponsors/drknowhow" target="_blank" rel="noopener" style="color:#EA4AAA">♥ Sponsor</a>
|
|
@@ -258,6 +259,9 @@
|
|
|
258
259
|
<a href="credentials.html" class="sidebar-link">
|
|
259
260
|
<span class="icon">🔐</span> Credential Vault
|
|
260
261
|
</a>
|
|
262
|
+
<a href="access.html" class="sidebar-link">
|
|
263
|
+
<span class="icon">🛡️</span> Access Guard
|
|
264
|
+
</a>
|
|
261
265
|
<a href="bitbucket.html" class="sidebar-link">
|
|
262
266
|
<span class="icon">🪣</span> Bitbucket
|
|
263
267
|
</a>
|
|
@@ -321,6 +325,11 @@
|
|
|
321
325
|
<div class="nav-card-title">Credential Vault</div>
|
|
322
326
|
<div class="nav-card-desc">Secrets agents can use but never read — keyring storage, injection at the subprocess boundary, cross-project search (v2.58.0+)</div>
|
|
323
327
|
</a>
|
|
328
|
+
<a href="access.html" class="nav-card">
|
|
329
|
+
<span class="nav-card-icon">🛡️</span>
|
|
330
|
+
<div class="nav-card-title">Access Guard</div>
|
|
331
|
+
<div class="nav-card-desc">Paths agents must never read or write — deny/read-only globs enforced at every C3 surface, with honest coverage limits (v2.62.0+)</div>
|
|
332
|
+
</a>
|
|
324
333
|
<a href="bitbucket.html" class="nav-card">
|
|
325
334
|
<span class="nav-card-icon">🪣</span>
|
|
326
335
|
<div class="nav-card-title">Bitbucket Data Center</div>
|
cli/guide/oracle.html
CHANGED
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
<a href="workflow.html">Workflow</a>
|
|
77
77
|
<a href="tools.html">Tools</a>
|
|
78
78
|
<a href="credentials.html">Credentials</a>
|
|
79
|
+
<a href="access.html">Access</a>
|
|
79
80
|
<a href="bitbucket.html">Bitbucket</a>
|
|
80
81
|
<a href="oracle.html" class="active">Oracle</a>
|
|
81
82
|
<a href="https://github.com/sponsors/drknowhow" target="_blank" rel="noopener" style="color:#EA4AAA">♥ Sponsor</a>
|
cli/guide/tools.html
CHANGED
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
<a href="workflow.html">Workflow</a>
|
|
81
81
|
<a href="tools.html" class="active">Tools</a>
|
|
82
82
|
<a href="credentials.html">Credentials</a>
|
|
83
|
+
<a href="access.html">Access</a>
|
|
83
84
|
<a href="bitbucket.html">Bitbucket</a>
|
|
84
85
|
<a href="oracle.html">Oracle</a>
|
|
85
86
|
<a href="https://github.com/sponsors/drknowhow" target="_blank" rel="noopener" style="color:#EA4AAA">♥ Sponsor</a>
|