damage-control-dev 0.1.1__tar.gz → 0.1.3__tar.gz
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.
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/PKG-INFO +6 -3
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/README.md +5 -2
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/__init__.py +1 -1
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/cli.py +1 -1
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/engine.py +124 -31
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/core.json +15 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/core.yaml +20 -0
- damage_control_dev-0.1.3/tools/check_links.py +85 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/tools/coverage_matrix.py +6 -2
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/.gitignore +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/LICENSE +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/NOTICE +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/pyproject.toml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/__main__.py +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/adapter.py +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/audit.py +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/breakglass.py +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/claude_code.py +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/groups.py +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/harness_config.py +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/hook.py +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/install.py +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/packs.py +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/policy.py +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/profile.py +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/aws.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/aws.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/cloud-hosting.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/cloud-hosting.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/containers.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/containers.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/control-plane.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/control-plane.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/databases.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/databases.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/firebase.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/firebase.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/gcp.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/gcp.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/git.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/git.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/infrastructure.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/infrastructure.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/mcp-tools.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/mcp-tools.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/outbound.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/outbound.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/payloads.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/payloads.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/publish.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/publish.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/registry.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/registry.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/remote.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/remote.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/tmux.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/tmux.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/ruleset.py +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/aws.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/aws.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/docker.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/docker.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/gcp.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/gcp.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/gh.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/gh.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/git.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/git.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/kubectl.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/kubectl.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/npm.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/npm.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/terraform.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/terraform.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/uv.json +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/uv.yaml +0 -0
- {damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/tools/build_rules_json.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: damage-control-dev
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Damage control for AI coding agents: a PreToolUse guard that blocks or asks before destructive commands and protected-path writes (Claude Code).
|
|
5
5
|
Project-URL: Homepage, https://github.com/dotdevdotdev/damage-control-dev
|
|
6
6
|
Project-URL: Source, https://github.com/dotdevdotdev/damage-control-dev
|
|
@@ -43,7 +43,7 @@ Supports **Claude Code on macOS and Linux**, Python 3.9 or newer. Nothing else i
|
|
|
43
43
|
- Protects its own control plane: the hook scripts, the settings that register them, and its policy file cannot be edited by the agent it guards.
|
|
44
44
|
- Fails closed: any crash or import failure in the hook exits 2 (block), and `init` registers the hook with `onFailure: block` (Claude Code 2.1.295 or newer) so a missing interpreter blocks the call instead of silently allowing it.
|
|
45
45
|
- Unattended mode: when no human can answer a prompt, an `ask` becomes a block unless a rule carries an explicit grant.
|
|
46
|
-
- Rules ship as groups that are on by default (core, git, registry, payloads, remote,
|
|
46
|
+
- Rules ship as groups that are on by default (control-plane, core, git, uv, registry, payloads, remote, mcp-tools, outbound) and opt-in vendor packs: `aws`, `gcp`, `firebase`, `cloud-hosting`, `databases`, `containers`, `infrastructure`, `publish`, `tmux`. Every rule has a frozen, stable id.
|
|
47
47
|
- Offline: after `init` nothing touches the network.
|
|
48
48
|
|
|
49
49
|
```
|
|
@@ -65,12 +65,13 @@ See [docs/threat-model.md](docs/threat-model.md).
|
|
|
65
65
|
|
|
66
66
|
## Where things live
|
|
67
67
|
|
|
68
|
-
Everything the installer keeps is under `~/.damage-control/` (or `$DAMAGE_CONTROL_HOME`)
|
|
68
|
+
Everything the installer keeps is under `~/.damage-control/` (or `$DAMAGE_CONTROL_HOME`), except the policy file, which is always `~/.damage-control/damagecontrol.yml`:
|
|
69
69
|
|
|
70
70
|
- `damagecontrol.yml`: the protected policy (kill switch, `packs:`, `disabled_groups:`)
|
|
71
71
|
- `damage-control/`, `tooldefs/`: your own rule overlays
|
|
72
72
|
- `state/install.json`: what `init` recorded (interpreter, ruleset digest, per-scope write hash); `state/backups/` holds first-write backups of your settings file
|
|
73
73
|
- `bin/damage-control-breakglass`: a stdlib-only script that unregisters the hooks if the Python environment is ever broken
|
|
74
|
+
- `logs/damage-control/<date>.jsonl`: the audit log, one JSON line per decision
|
|
74
75
|
|
|
75
76
|
A policy with no `packs:` key loads the default groups and no vendor packs; an unreadable policy loads everything.
|
|
76
77
|
|
|
@@ -78,6 +79,8 @@ If the environment breaks (for example a `uv tool install --force` interrupted m
|
|
|
78
79
|
|
|
79
80
|
## Docs
|
|
80
81
|
|
|
82
|
+
Start at [docs/index.md](docs/index.md).
|
|
83
|
+
|
|
81
84
|
- [docs/threat-model.md](docs/threat-model.md): what it protects against and the residuals
|
|
82
85
|
- [docs/coverage.md](docs/coverage.md): which tools are covered and how
|
|
83
86
|
- [docs/rule-authoring.md](docs/rule-authoring.md): writing rules, packs and overlays
|
|
@@ -19,7 +19,7 @@ Supports **Claude Code on macOS and Linux**, Python 3.9 or newer. Nothing else i
|
|
|
19
19
|
- Protects its own control plane: the hook scripts, the settings that register them, and its policy file cannot be edited by the agent it guards.
|
|
20
20
|
- Fails closed: any crash or import failure in the hook exits 2 (block), and `init` registers the hook with `onFailure: block` (Claude Code 2.1.295 or newer) so a missing interpreter blocks the call instead of silently allowing it.
|
|
21
21
|
- Unattended mode: when no human can answer a prompt, an `ask` becomes a block unless a rule carries an explicit grant.
|
|
22
|
-
- Rules ship as groups that are on by default (core, git, registry, payloads, remote,
|
|
22
|
+
- Rules ship as groups that are on by default (control-plane, core, git, uv, registry, payloads, remote, mcp-tools, outbound) and opt-in vendor packs: `aws`, `gcp`, `firebase`, `cloud-hosting`, `databases`, `containers`, `infrastructure`, `publish`, `tmux`. Every rule has a frozen, stable id.
|
|
23
23
|
- Offline: after `init` nothing touches the network.
|
|
24
24
|
|
|
25
25
|
```
|
|
@@ -41,12 +41,13 @@ See [docs/threat-model.md](docs/threat-model.md).
|
|
|
41
41
|
|
|
42
42
|
## Where things live
|
|
43
43
|
|
|
44
|
-
Everything the installer keeps is under `~/.damage-control/` (or `$DAMAGE_CONTROL_HOME`)
|
|
44
|
+
Everything the installer keeps is under `~/.damage-control/` (or `$DAMAGE_CONTROL_HOME`), except the policy file, which is always `~/.damage-control/damagecontrol.yml`:
|
|
45
45
|
|
|
46
46
|
- `damagecontrol.yml`: the protected policy (kill switch, `packs:`, `disabled_groups:`)
|
|
47
47
|
- `damage-control/`, `tooldefs/`: your own rule overlays
|
|
48
48
|
- `state/install.json`: what `init` recorded (interpreter, ruleset digest, per-scope write hash); `state/backups/` holds first-write backups of your settings file
|
|
49
49
|
- `bin/damage-control-breakglass`: a stdlib-only script that unregisters the hooks if the Python environment is ever broken
|
|
50
|
+
- `logs/damage-control/<date>.jsonl`: the audit log, one JSON line per decision
|
|
50
51
|
|
|
51
52
|
A policy with no `packs:` key loads the default groups and no vendor packs; an unreadable policy loads everything.
|
|
52
53
|
|
|
@@ -54,6 +55,8 @@ If the environment breaks (for example a `uv tool install --force` interrupted m
|
|
|
54
55
|
|
|
55
56
|
## Docs
|
|
56
57
|
|
|
58
|
+
Start at [docs/index.md](docs/index.md).
|
|
59
|
+
|
|
57
60
|
- [docs/threat-model.md](docs/threat-model.md): what it protects against and the residuals
|
|
58
61
|
- [docs/coverage.md](docs/coverage.md): which tools are covered and how
|
|
59
62
|
- [docs/rule-authoring.md](docs/rule-authoring.md): writing rules, packs and overlays
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""``damage-control-dev``: install, inspect and operate the Claude Code hooks
|
|
1
|
+
"""``damage-control-dev``: install, inspect and operate the Claude Code hooks.
|
|
2
2
|
|
|
3
3
|
damage-control-dev init [--scope user|project|managed] [--dry-run] [--refresh] [--fail-open]
|
|
4
4
|
damage-control-dev status [--json] # read-only drift report
|
|
@@ -24,7 +24,7 @@ import signal
|
|
|
24
24
|
import sys
|
|
25
25
|
import threading
|
|
26
26
|
from pathlib import Path
|
|
27
|
-
from typing import Any, Dict, Iterator, List, NamedTuple, Optional, Set, Tuple
|
|
27
|
+
from typing import Any, Callable, Dict, Iterator, List, NamedTuple, Optional, Set, Tuple
|
|
28
28
|
|
|
29
29
|
from damage_control import audit
|
|
30
30
|
from damage_control.profile import GENERIC
|
|
@@ -857,7 +857,11 @@ def _path_context_regex(path: str) -> str:
|
|
|
857
857
|
return r'["\']?(?:[^\s"\';|&<>]*/)?' + glob_to_regex(path)
|
|
858
858
|
expanded = os.path.expanduser(path)
|
|
859
859
|
if expanded == path:
|
|
860
|
-
|
|
860
|
+
if path.startswith("/"):
|
|
861
|
+
return re.escape(path)
|
|
862
|
+
# A bare name (`.git/hooks/`, `node_modules/`, `CLAUDE.md`) is a path
|
|
863
|
+
# component: the same entry guards `/repo/.git/hooks/x` as `.git/hooks/x`.
|
|
864
|
+
return r'["\']?(?:[^\s"\';|&<>]*/)?' + re.escape(path)
|
|
861
865
|
return r'(?:' + re.escape(expanded) + r'|' + re.escape(path) + r')'
|
|
862
866
|
|
|
863
867
|
|
|
@@ -917,7 +921,18 @@ def path_ladder_haystacks(command: str, path: str) -> List[str]:
|
|
|
917
921
|
canon = canonicalize_command(token)
|
|
918
922
|
return canon != token and bool(keep.search(canon))
|
|
919
923
|
|
|
920
|
-
|
|
924
|
+
# ``~``/``$HOME``/``${HOME}`` name the home dir and ``{a,b}`` expands before the
|
|
925
|
+
# verb runs: the ladders judge those spellings too, as the zero-access scan does.
|
|
926
|
+
spellings = [command]
|
|
927
|
+
canon = canonicalize_command(command)
|
|
928
|
+
if canon != command:
|
|
929
|
+
spellings.append(canon)
|
|
930
|
+
for text in list(spellings):
|
|
931
|
+
spellings.extend(v for v in brace_variants(text) or () if v not in spellings)
|
|
932
|
+
haystacks: List[str] = []
|
|
933
|
+
for text in spellings:
|
|
934
|
+
haystacks += [h for h in masked_subcommands(mask_sed_substitutions(text), keep=_mentions_path) if h not in haystacks]
|
|
935
|
+
return haystacks
|
|
921
936
|
|
|
922
937
|
|
|
923
938
|
def check_path_patterns(
|
|
@@ -1500,7 +1515,7 @@ def _record_load_failure(
|
|
|
1500
1515
|
detail = getattr(exc, "problem", None) or str(exc) or type(exc).__name__
|
|
1501
1516
|
where = f"{path}:{line}" if line else str(path)
|
|
1502
1517
|
try:
|
|
1503
|
-
print(f"
|
|
1518
|
+
print(f"damage-control: SKIPPED {where}: {detail}", file=sys.stderr)
|
|
1504
1519
|
except Exception:
|
|
1505
1520
|
# A dead stderr must not turn a reporting line into a changed verdict.
|
|
1506
1521
|
pass
|
|
@@ -2782,7 +2797,7 @@ def unattended_grant_allows(
|
|
|
2782
2797
|
# to a looser layer — see parse_unattended_allow.
|
|
2783
2798
|
return False, (
|
|
2784
2799
|
f"rule '{rule_id}' is named in unattended_allow but its entry is malformed, "
|
|
2785
|
-
f"so it grants nothing
|
|
2800
|
+
f"so it grants nothing"
|
|
2786
2801
|
)
|
|
2787
2802
|
if any(not s for s in scopes):
|
|
2788
2803
|
return True, f"rule '{rule_id}' is granted unattended (unscoped)"
|
|
@@ -5594,11 +5609,11 @@ def _ansi_c_fold(text: str) -> str:
|
|
|
5594
5609
|
return _ANSI_C.sub(_dec, text)
|
|
5595
5610
|
|
|
5596
5611
|
|
|
5597
|
-
def _resolve_word(word: str, cwd: str) -> str:
|
|
5598
|
-
"""``word`` (already unquoted) made absolute when it names a
|
|
5612
|
+
def _resolve_word(word: str, cwd: str, guarded: Callable[[str], bool]) -> str:
|
|
5613
|
+
"""``word`` (already unquoted) made absolute when it names a ``guarded`` path under ``cwd``."""
|
|
5599
5614
|
opt = _OPT_VALUE.fullmatch(word)
|
|
5600
5615
|
if opt: # of=<file>, --output=<file>, --output-document=<file>, -o=<file>
|
|
5601
|
-
return opt.group(1) + _resolve_word(opt.group(2), cwd)
|
|
5616
|
+
return opt.group(1) + _resolve_word(opt.group(2), cwd, guarded)
|
|
5602
5617
|
if word.startswith("-"):
|
|
5603
5618
|
return word
|
|
5604
5619
|
if _PLAIN_WORD.fullmatch(word):
|
|
@@ -5606,22 +5621,16 @@ def _resolve_word(word: str, cwd: str) -> str:
|
|
|
5606
5621
|
if re.search(r"[*?\[]", word):
|
|
5607
5622
|
hits = _glob_hits(joined)
|
|
5608
5623
|
return hits[0] if hits else word
|
|
5609
|
-
return joined if
|
|
5624
|
+
return joined if guarded(joined) else word
|
|
5610
5625
|
|
|
5611
5626
|
def _sub(m: "re.Match[str]") -> str: # a word with quotes inside (python -c "open('x')")
|
|
5612
5627
|
cand = posixpath.normpath(posixpath.join(cwd, m.group(1)))
|
|
5613
|
-
return cand if
|
|
5628
|
+
return cand if guarded(cand) else m.group(0)
|
|
5614
5629
|
|
|
5615
5630
|
return _REL_WORD.sub(_sub, word)
|
|
5616
5631
|
|
|
5617
5632
|
|
|
5618
|
-
def
|
|
5619
|
-
"""Rewrite words in ``text`` that name a protected file under ``cwd``.
|
|
5620
|
-
|
|
5621
|
-
The text is first folded through what the shell does to a word before the
|
|
5622
|
-
program sees it — ANSI-C ``$'…'``, ``$PWD``, adjacent quotes, backslashes —
|
|
5623
|
-
so the relative spelling reaches the same check the absolute one does.
|
|
5624
|
-
"""
|
|
5633
|
+
def _absolutize_line(text: str, cwd: str, guarded: Callable[[str], bool]) -> str:
|
|
5625
5634
|
folded = _ansi_c_fold(text).replace("${PWD}", cwd).replace("$PWD", cwd)
|
|
5626
5635
|
try:
|
|
5627
5636
|
lex = shlex.shlex(folded, posix=True, punctuation_chars=True)
|
|
@@ -5629,14 +5638,26 @@ def _absolutize(text: str, cwd: str) -> str:
|
|
|
5629
5638
|
words = list(lex)
|
|
5630
5639
|
except ValueError:
|
|
5631
5640
|
return _REL_WORD.sub(
|
|
5632
|
-
lambda m: _resolve_word(m.group(1), cwd), folded
|
|
5641
|
+
lambda m: _resolve_word(m.group(1), cwd, guarded), folded
|
|
5633
5642
|
)
|
|
5634
|
-
resolved = [w if _PUNCT_WORD.fullmatch(w) else _resolve_word(w, cwd) for w in words]
|
|
5643
|
+
resolved = [w if _PUNCT_WORD.fullmatch(w) else _resolve_word(w, cwd, guarded) for w in words]
|
|
5635
5644
|
if resolved == words and folded == text:
|
|
5636
5645
|
return text
|
|
5637
5646
|
return " ".join(w if _PUNCT_WORD.fullmatch(w) else _quote_word(w) for w in resolved)
|
|
5638
5647
|
|
|
5639
5648
|
|
|
5649
|
+
def _absolutize(text: str, cwd: str, guarded: Callable[[str], bool] = is_protected_control_plane) -> str:
|
|
5650
|
+
"""Rewrite words in ``text`` that name a ``guarded`` path under ``cwd`` (default: the control plane).
|
|
5651
|
+
|
|
5652
|
+
The text is first folded through what the shell does to a word before the
|
|
5653
|
+
program sees it — ANSI-C ``$'…'``, ``$PWD``, adjacent quotes, backslashes —
|
|
5654
|
+
so the relative spelling reaches the same check the absolute one does.
|
|
5655
|
+
Each line is rewritten on its own: a newline separates commands, and
|
|
5656
|
+
flattening it would pull a later command's verb out of command position.
|
|
5657
|
+
"""
|
|
5658
|
+
return "\n".join(_absolutize_line(line, cwd, guarded) for line in text.split("\n"))
|
|
5659
|
+
|
|
5660
|
+
|
|
5640
5661
|
_ASSIGN_STMT = _rx(
|
|
5641
5662
|
r"(?:^|[;&|\n({]|\b(?:then|do|else))[ \t]*(?:(?:export|local|declare|readonly)[ \t]+)?(?P<n>[A-Za-z_]\w*)=(?P<v>(?:'[^']*'|\"[^\"]*\"|[^\s;&|()<>'\"])*)(?=[ \t]*(?:[;&|\n]|$))"
|
|
5642
5663
|
)
|
|
@@ -5938,6 +5959,19 @@ def _alias_target(path: str) -> Optional[str]:
|
|
|
5938
5959
|
return None
|
|
5939
5960
|
|
|
5940
5961
|
|
|
5962
|
+
def _alias_variants(rest: str, cwd: str) -> List[str]:
|
|
5963
|
+
"""``rest`` with each relative word that is an existing alias of a protected file spelled as that file."""
|
|
5964
|
+
out: List[str] = []
|
|
5965
|
+
for m in itertools.islice(_PATH_WORD.finditer(rest), _REALPATH_WORD_CAP):
|
|
5966
|
+
word = m.group(0)
|
|
5967
|
+
if word.startswith(("/", "~", "-")) or len(word) < 2:
|
|
5968
|
+
continue
|
|
5969
|
+
target = _alias_target(posixpath.normpath(posixpath.join(cwd, word)))
|
|
5970
|
+
if target:
|
|
5971
|
+
out.append(rest.replace(word, _quote_word(target)))
|
|
5972
|
+
return out
|
|
5973
|
+
|
|
5974
|
+
|
|
5941
5975
|
def _cwd_realpath_variants(command: str) -> List[str]:
|
|
5942
5976
|
"""``_realpath_variants`` for a relative word after a literal ``cd <dir> &&`` (#1065).
|
|
5943
5977
|
|
|
@@ -5945,16 +5979,64 @@ def _cwd_realpath_variants(command: str) -> List[str]:
|
|
|
5945
5979
|
``_cwd_variants`` only rewrites words that are lexically protected. The
|
|
5946
5980
|
aliased word is resolved against the same cwd and judged as what it is.
|
|
5947
5981
|
"""
|
|
5982
|
+
return [new for cwd, rest in _cwd_scopes(command)[0] for new in _alias_variants(rest, cwd)]
|
|
5983
|
+
|
|
5984
|
+
|
|
5985
|
+
def _guarded_path_test(config: Dict[str, Any]) -> Callable[[str], bool]:
|
|
5986
|
+
"""Whether an absolute path is guarded by ANY class: control plane, zero-access, read-only, no-delete."""
|
|
5987
|
+
patterns = [
|
|
5988
|
+
p
|
|
5989
|
+
for key in ("zeroAccessPaths", "readOnlyPaths", "noDeletePaths")
|
|
5990
|
+
for p in (config.get(key) or [])
|
|
5991
|
+
if isinstance(p, str)
|
|
5992
|
+
]
|
|
5993
|
+
# A bare directory entry (`.git/`, `node_modules/`) is a path COMPONENT, not an
|
|
5994
|
+
# absolute prefix: a file inside it is guarded wherever the directory sits, so
|
|
5995
|
+
# `rm hooks/x` run from `<repo>/.git` names a guarded path without saying `.git`.
|
|
5996
|
+
bare_dirs = [
|
|
5997
|
+
"/" + p for p in patterns
|
|
5998
|
+
if p.endswith("/") and not p.startswith(("/", "~")) and not is_glob_pattern(p)
|
|
5999
|
+
]
|
|
6000
|
+
|
|
6001
|
+
def guarded(path: str) -> bool:
|
|
6002
|
+
if is_protected_control_plane(path) or any(match_path(path, p) for p in patterns):
|
|
6003
|
+
return True
|
|
6004
|
+
fold = str.lower if _casefolds_paths(path) else str
|
|
6005
|
+
probe = fold(path) + "/"
|
|
6006
|
+
return any(fold(d) in probe for d in bare_dirs)
|
|
6007
|
+
|
|
6008
|
+
return guarded
|
|
6009
|
+
|
|
6010
|
+
|
|
6011
|
+
def _before_first_cd(command: str) -> str:
|
|
6012
|
+
"""The text that runs in the payload cwd: everything before the first ``cd`` / ``pushd`` / ``popd``."""
|
|
6013
|
+
starts = [m.start() for rx in (_CD_RE, _POPD_RE) for m in itertools.islice(rx.finditer(command), 1)]
|
|
6014
|
+
return command[: min(starts)] if starts else command
|
|
6015
|
+
|
|
6016
|
+
|
|
6017
|
+
def _cwd_resolved_variants(command: str, cwd: Optional[str], config: Dict[str, Any]) -> List[str]:
|
|
6018
|
+
"""``command`` with each relative operand that names a guarded path spelled absolutely.
|
|
6019
|
+
|
|
6020
|
+
Relative operands are resolved against the payload ``cwd`` — and its
|
|
6021
|
+
``realpath``, when the cwd is itself a symlink — for the text that runs
|
|
6022
|
+
there, and against each literal ``cd <dir> &&`` / ``git -C`` / ``env -C``
|
|
6023
|
+
scope for the text that runs in it. The absolute spelling is then judged by
|
|
6024
|
+
the same rules as any other, so ``> .claude/settings.json`` from ``$HOME`` is
|
|
6025
|
+
``> ~/.claude/settings.json``. A missing cwd adds nothing: what a relative
|
|
6026
|
+
word means is then unknown, which the unverifiable-target verdict owns.
|
|
6027
|
+
"""
|
|
6028
|
+
scopes = list(_cwd_scopes(command)[0])
|
|
6029
|
+
if cwd and os.path.isabs(cwd):
|
|
6030
|
+
lead = _before_first_cd(command)
|
|
6031
|
+
scopes += [(base, lead) for base in dict.fromkeys((posixpath.normpath(cwd), os.path.realpath(cwd)))]
|
|
6032
|
+
guarded = _guarded_path_test(config)
|
|
5948
6033
|
out: List[str] = []
|
|
5949
|
-
for
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
if target:
|
|
5956
|
-
out.append(rest.replace(word, _quote_word(target)))
|
|
5957
|
-
return out
|
|
6034
|
+
for base, text in scopes:
|
|
6035
|
+
new = _absolutize(text, base, guarded)
|
|
6036
|
+
if new != text:
|
|
6037
|
+
out.append(new)
|
|
6038
|
+
out.extend(_alias_variants(text, base))
|
|
6039
|
+
return list(dict.fromkeys(out))
|
|
5958
6040
|
|
|
5959
6041
|
|
|
5960
6042
|
def _cwd_variants(command: str) -> List[str]:
|
|
@@ -9920,7 +10002,7 @@ def audit_best_effort(log: Any) -> Any:
|
|
|
9920
10002
|
log(*args, **kwargs)
|
|
9921
10003
|
except Exception as e:
|
|
9922
10004
|
try:
|
|
9923
|
-
print(f"
|
|
10005
|
+
print(f"damage-control: audit log failed ({type(e).__name__}) — decision unchanged",
|
|
9924
10006
|
file=sys.stderr)
|
|
9925
10007
|
except Exception:
|
|
9926
10008
|
pass
|
|
@@ -10020,6 +10102,15 @@ def _decide_command(
|
|
|
10020
10102
|
outer one.
|
|
10021
10103
|
"""
|
|
10022
10104
|
verdict = _decide_rules(command, config, cwd)
|
|
10105
|
+
resolved = []
|
|
10106
|
+
if not verdict.get("protected"):
|
|
10107
|
+
# The control plane outranks the escape hatch and the kill switch, so a
|
|
10108
|
+
# relative spelling of it is judged before either can end the walk.
|
|
10109
|
+
for variant in _cwd_resolved_variants(command, cwd, config):
|
|
10110
|
+
inner = _decide_rules(variant, config, cwd)
|
|
10111
|
+
if inner.get("protected"):
|
|
10112
|
+
return dict(inner, command=command)
|
|
10113
|
+
resolved.append(inner)
|
|
10023
10114
|
if (
|
|
10024
10115
|
_depth >= _RESCAN_DEPTH
|
|
10025
10116
|
or verdict.get("protected")
|
|
@@ -10028,8 +10119,10 @@ def _decide_command(
|
|
|
10028
10119
|
or verdict["decision"] == "block"
|
|
10029
10120
|
):
|
|
10030
10121
|
return verdict
|
|
10031
|
-
for
|
|
10032
|
-
|
|
10122
|
+
for inner in itertools.chain(
|
|
10123
|
+
resolved,
|
|
10124
|
+
(_decide_command(hidden, config, cwd, _depth + 1) for hidden in _hidden_commands(command, rules=True)),
|
|
10125
|
+
):
|
|
10033
10126
|
if _DECISION_RANK[inner["decision"]] > _DECISION_RANK[verdict["decision"]] and not (
|
|
10034
10127
|
inner.get("escape") or inner.get("disabled")
|
|
10035
10128
|
):
|
|
@@ -191,6 +191,11 @@
|
|
|
191
191
|
"~/.pypirc",
|
|
192
192
|
"~/.git-credentials",
|
|
193
193
|
".git-credentials",
|
|
194
|
+
"~/.config/gh/hosts.yml",
|
|
195
|
+
"~/.cargo/credentials.toml",
|
|
196
|
+
"~/.cargo/credentials",
|
|
197
|
+
"~/.gem/credentials",
|
|
198
|
+
"~/.terraform.d/credentials.tfrc.json",
|
|
194
199
|
"dump.sql",
|
|
195
200
|
"backup.sql",
|
|
196
201
|
"*.dump"
|
|
@@ -209,6 +214,16 @@
|
|
|
209
214
|
"~/.zshrc",
|
|
210
215
|
"~/.profile",
|
|
211
216
|
"~/.bash_profile",
|
|
217
|
+
"~/.bash_login",
|
|
218
|
+
"~/.bash_logout",
|
|
219
|
+
"~/.zshenv",
|
|
220
|
+
"~/.zprofile",
|
|
221
|
+
"~/.zlogin",
|
|
222
|
+
"~/.zlogout",
|
|
223
|
+
"~/.gitconfig",
|
|
224
|
+
"~/.config/git/",
|
|
225
|
+
".git/config",
|
|
226
|
+
".git/hooks/",
|
|
212
227
|
"package-lock.json",
|
|
213
228
|
"yarn.lock",
|
|
214
229
|
"pnpm-lock.yaml",
|
|
@@ -255,6 +255,11 @@ zeroAccessPaths:
|
|
|
255
255
|
- "~/.pypirc"
|
|
256
256
|
- "~/.git-credentials"
|
|
257
257
|
- ".git-credentials"
|
|
258
|
+
- "~/.config/gh/hosts.yml"
|
|
259
|
+
- "~/.cargo/credentials.toml"
|
|
260
|
+
- "~/.cargo/credentials"
|
|
261
|
+
- "~/.gem/credentials"
|
|
262
|
+
- "~/.terraform.d/credentials.tfrc.json"
|
|
258
263
|
|
|
259
264
|
# ---------------------------------------------------------------------------
|
|
260
265
|
# DATABASE DUMPS (may contain production data)
|
|
@@ -292,6 +297,21 @@ readOnlyPaths:
|
|
|
292
297
|
- ~/.zshrc
|
|
293
298
|
- ~/.profile
|
|
294
299
|
- ~/.bash_profile
|
|
300
|
+
- ~/.bash_login
|
|
301
|
+
- ~/.bash_logout
|
|
302
|
+
- ~/.zshenv
|
|
303
|
+
- ~/.zprofile
|
|
304
|
+
- ~/.zlogin
|
|
305
|
+
- ~/.zlogout
|
|
306
|
+
|
|
307
|
+
# ---------------------------------------------------------------------------
|
|
308
|
+
# GIT CONFIG AND HOOKS (#1255) - keys such as core.fsmonitor, core.pager and
|
|
309
|
+
# alias.x=!... run programs, and a hook runs on the next git operation
|
|
310
|
+
# ---------------------------------------------------------------------------
|
|
311
|
+
- ~/.gitconfig
|
|
312
|
+
- ~/.config/git/
|
|
313
|
+
- .git/config
|
|
314
|
+
- .git/hooks/
|
|
295
315
|
|
|
296
316
|
# ---------------------------------------------------------------------------
|
|
297
317
|
# LOCK FILES - Never manually edit, use package managers
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Fail if a relative link in the repository's Markdown files does not resolve.
|
|
3
|
+
|
|
4
|
+
Checks ``[text](target)`` links in README.md, SECURITY.md, CONTRIBUTING.md and docs/*.md:
|
|
5
|
+
the target file must exist, and a ``#fragment`` must match a heading in that file (GitHub's
|
|
6
|
+
slug rules) or an explicit ``<a id=...>``. External ``http(s)``/``mailto`` links are not fetched.
|
|
7
|
+
|
|
8
|
+
check_links.py # exit 1 and list every broken link
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import List, Set
|
|
17
|
+
|
|
18
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
19
|
+
LINK_RE = re.compile(r"(?<!\!)\[[^\]]*\]\(([^)\s]+)\)")
|
|
20
|
+
HEADING_RE = re.compile(r"^(#{1,6})\s+(.*?)\s*#*\s*$")
|
|
21
|
+
ANCHOR_RE = re.compile(r'<a\s+(?:id|name)="([^"]+)"')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def slug(heading: str) -> str:
|
|
25
|
+
"""GitHub's heading anchor: lowercase, drop punctuation except ``-`` and ``_``, spaces to ``-``."""
|
|
26
|
+
text = re.sub(r"`", "", heading.strip().lower())
|
|
27
|
+
text = re.sub(r"[^\w\- ]", "", text)
|
|
28
|
+
return text.replace(" ", "-")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def anchors(path: Path) -> Set[str]:
|
|
32
|
+
found: Set[str] = set()
|
|
33
|
+
counts: dict = {}
|
|
34
|
+
in_fence = False
|
|
35
|
+
for line in path.read_text().splitlines():
|
|
36
|
+
if line.startswith("```"):
|
|
37
|
+
in_fence = not in_fence
|
|
38
|
+
continue
|
|
39
|
+
if in_fence:
|
|
40
|
+
continue
|
|
41
|
+
m = HEADING_RE.match(line)
|
|
42
|
+
if m:
|
|
43
|
+
base = slug(m.group(2))
|
|
44
|
+
n = counts.get(base, 0)
|
|
45
|
+
counts[base] = n + 1
|
|
46
|
+
found.add(base if n == 0 else f"{base}-{n}")
|
|
47
|
+
found.update(ANCHOR_RE.findall(line))
|
|
48
|
+
return found
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def markdown_files() -> List[Path]:
|
|
52
|
+
return sorted([*ROOT.glob("*.md"), *ROOT.glob("docs/*.md")])
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def check(path: Path) -> List[str]:
|
|
56
|
+
problems: List[str] = []
|
|
57
|
+
in_fence = False
|
|
58
|
+
for number, line in enumerate(path.read_text().splitlines(), 1):
|
|
59
|
+
if line.startswith("```"):
|
|
60
|
+
in_fence = not in_fence
|
|
61
|
+
continue
|
|
62
|
+
if in_fence:
|
|
63
|
+
continue
|
|
64
|
+
for target in LINK_RE.findall(line):
|
|
65
|
+
if re.match(r"[a-z][a-z0-9+.-]*:", target):
|
|
66
|
+
continue
|
|
67
|
+
name, _, fragment = target.partition("#")
|
|
68
|
+
dest = (path.parent / name).resolve() if name else path
|
|
69
|
+
where = f"{path.relative_to(ROOT)}:{number}"
|
|
70
|
+
if not dest.exists():
|
|
71
|
+
problems.append(f"{where}: {target}: no such file")
|
|
72
|
+
elif fragment and dest.suffix == ".md" and fragment not in anchors(dest):
|
|
73
|
+
problems.append(f"{where}: {target}: no heading '#{fragment}' in {dest.relative_to(ROOT)}")
|
|
74
|
+
return problems
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def main() -> int:
|
|
78
|
+
problems = [p for f in markdown_files() for p in check(f)]
|
|
79
|
+
for problem in problems:
|
|
80
|
+
print(problem, file=sys.stderr)
|
|
81
|
+
return 1 if problems else 0
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
sys.exit(main())
|
|
@@ -30,7 +30,7 @@ def render() -> str:
|
|
|
30
30
|
for matcher, hook in claude_code.MATCHERS.items():
|
|
31
31
|
tools, shell = adapter.HOOK_KINDS[hook]
|
|
32
32
|
if tools is None:
|
|
33
|
-
kind, judges = "mcp", "the tool name and its path-valued arguments, plus a
|
|
33
|
+
kind, judges = "mcp", "the tool name and its path-valued arguments, plus, where a host profile maps the tool, the shell command it runs"
|
|
34
34
|
else:
|
|
35
35
|
names = [t for t, (_, _) in tools.items() if matcher == "mcp__.*" or t == matcher]
|
|
36
36
|
kind = tools[names[0]][0] if names else "?"
|
|
@@ -42,7 +42,11 @@ def render() -> str:
|
|
|
42
42
|
return "\n".join([
|
|
43
43
|
"# Coverage",
|
|
44
44
|
"",
|
|
45
|
-
"
|
|
45
|
+
"Which Claude Code tools damage control judges, and how. Anything not listed is not judged; see the",
|
|
46
|
+
"[threat model](threat-model.md#what-it-does-not-protect-against).",
|
|
47
|
+
"",
|
|
48
|
+
"This page is generated by `tools/coverage_matrix.py` from the hooks' own tables; do not edit it by hand.",
|
|
49
|
+
"CI fails if the committed file is stale. Regenerate with `python tools/coverage_matrix.py`.",
|
|
46
50
|
"",
|
|
47
51
|
"| Tool matcher | Hook | Judged on | Block | Ask | Audited |",
|
|
48
52
|
"|---|---|---|---|---|---|",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/cloud-hosting.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/cloud-hosting.yaml
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/containers.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/containers.yaml
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/control-plane.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/control-plane.yaml
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/databases.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/databases.yaml
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/firebase.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/firebase.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/infrastructure.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/infrastructure.yaml
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/mcp-tools.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/mcp-tools.yaml
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/outbound.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/outbound.yaml
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/payloads.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/payloads.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/registry.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/rules/registry.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/docker.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/docker.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/kubectl.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/kubectl.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/terraform.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.1 → damage_control_dev-0.1.3}/src/damage_control/tooldefs/terraform.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|