damage-control-dev 0.1.0__tar.gz → 0.1.2__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.2/PKG-INFO +95 -0
- damage_control_dev-0.1.2/README.md +71 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/pyproject.toml +11 -1
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/__init__.py +1 -1
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/cli.py +1 -1
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/engine.py +92 -29
- damage_control_dev-0.1.2/tools/check_links.py +85 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/tools/coverage_matrix.py +6 -2
- damage_control_dev-0.1.0/PKG-INFO +0 -15
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/.gitignore +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/LICENSE +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/NOTICE +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/__main__.py +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/adapter.py +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/audit.py +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/breakglass.py +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/claude_code.py +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/groups.py +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/harness_config.py +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/hook.py +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/install.py +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/packs.py +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/policy.py +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/profile.py +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/aws.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/aws.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/cloud-hosting.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/cloud-hosting.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/containers.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/containers.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/control-plane.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/control-plane.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/core.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/core.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/databases.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/databases.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/firebase.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/firebase.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/gcp.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/gcp.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/git.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/git.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/infrastructure.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/infrastructure.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/mcp-tools.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/mcp-tools.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/outbound.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/outbound.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/payloads.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/payloads.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/publish.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/publish.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/registry.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/registry.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/remote.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/remote.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/tmux.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/tmux.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/ruleset.py +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/aws.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/aws.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/docker.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/docker.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/gcp.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/gcp.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/gh.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/gh.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/git.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/git.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/kubectl.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/kubectl.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/npm.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/npm.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/terraform.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/terraform.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/uv.json +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/uv.yaml +0 -0
- {damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/tools/build_rules_json.py +0 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: damage-control-dev
|
|
3
|
+
Version: 0.1.2
|
|
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
|
+
Project-URL: Homepage, https://github.com/dotdevdotdev/damage-control-dev
|
|
6
|
+
Project-URL: Source, https://github.com/dotdevdotdev/damage-control-dev
|
|
7
|
+
Project-URL: Issues, https://github.com/dotdevdotdev/damage-control-dev/issues
|
|
8
|
+
Project-URL: Security, https://github.com/dotdevdotdev/damage-control-dev/security/policy
|
|
9
|
+
Author-email: dotdev <dev@dotdev.dev>
|
|
10
|
+
License: Apache-2.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
License-File: NOTICE
|
|
13
|
+
Keywords: agent,ai,claude-code,guardrails,hooks,safety
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Requires-Dist: pyyaml>=6.0
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# damage-control-dev
|
|
26
|
+
|
|
27
|
+
A guardrail layer for AI coding agents. It runs as a Claude Code `PreToolUse` hook and **blocks or asks before destructive commands, secret access, protected-path writes and risky MCP calls**, before the tool runs.
|
|
28
|
+
|
|
29
|
+
It is a seatbelt, not a sandbox. See [What it is not](#what-it-is-not).
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
uv tool install damage-control-dev # or: pipx install damage-control-dev
|
|
33
|
+
damage-control-dev init # register the hooks, self-test them, record the install
|
|
34
|
+
damage-control-dev status # read-only drift report
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Supports **Claude Code on macOS and Linux**, Python 3.9 or newer. Nothing else is supported or planned.
|
|
38
|
+
|
|
39
|
+
## What it does
|
|
40
|
+
|
|
41
|
+
- Guards Bash, Edit, Write, Read and MCP tool calls through one policy ladder (allow, ask, block).
|
|
42
|
+
- Re-scans wrapped commands as commands in their own right: `ssh host "..."`, `sh -c`, database clients (`psql -c`, `mysql -e`), `git -c` keys that run programs.
|
|
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
|
+
- 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
|
+
- 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 (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
|
+
- Offline: after `init` nothing touches the network.
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
damage-control-dev check "git push --force origin main" # what would the hooks decide?
|
|
51
|
+
damage-control-dev rules # list enforced rule ids
|
|
52
|
+
damage-control-dev packs # list packs; enabling is human-terminal only
|
|
53
|
+
damage-control-dev doctor --fix # self-test and repair
|
|
54
|
+
damage-control-dev uninstall # remove exactly our entries
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## What it is not
|
|
58
|
+
|
|
59
|
+
- Not a sandbox, and not a boundary against a determined adversary. A process running as the same OS user can edit anything this tool can.
|
|
60
|
+
- Only as strong as the harness's hook surface: commands you type yourself with `!`, tools that have no hook event, and a hook that times out are outside its reach.
|
|
61
|
+
- No network egress filtering, containment or resource limits.
|
|
62
|
+
- Expect false positives and false negatives. The policy is: ask, then learn.
|
|
63
|
+
|
|
64
|
+
See [docs/threat-model.md](docs/threat-model.md).
|
|
65
|
+
|
|
66
|
+
## Where things live
|
|
67
|
+
|
|
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
|
+
|
|
70
|
+
- `damagecontrol.yml`: the protected policy (kill switch, `packs:`, `disabled_groups:`)
|
|
71
|
+
- `damage-control/`, `tooldefs/`: your own rule overlays
|
|
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
|
+
- `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
|
|
75
|
+
|
|
76
|
+
A policy with no `packs:` key loads the default groups and no vendor packs; an unreadable policy loads everything.
|
|
77
|
+
|
|
78
|
+
If the environment breaks (for example a `uv tool install --force` interrupted mid-upgrade) and the hook cannot start, every tool call is blocked by design. Run `python3 ~/.damage-control/bin/damage-control-breakglass unregister` from a normal terminal, then reinstall and run `init` again.
|
|
79
|
+
|
|
80
|
+
## Docs
|
|
81
|
+
|
|
82
|
+
Start at [docs/index.md](docs/index.md).
|
|
83
|
+
|
|
84
|
+
- [docs/threat-model.md](docs/threat-model.md): what it protects against and the residuals
|
|
85
|
+
- [docs/coverage.md](docs/coverage.md): which tools are covered and how
|
|
86
|
+
- [docs/rule-authoring.md](docs/rule-authoring.md): writing rules, packs and overlays
|
|
87
|
+
- [docs/extending.md](docs/extending.md): profiles, the hook runtime, registration and repair
|
|
88
|
+
|
|
89
|
+
## Security
|
|
90
|
+
|
|
91
|
+
A bypass of a shipped rule that the threat model says should be caught is a vulnerability. Report it privately, see [SECURITY.md](SECURITY.md).
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
Apache-2.0. Contributions are signed off under the DCO, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# damage-control-dev
|
|
2
|
+
|
|
3
|
+
A guardrail layer for AI coding agents. It runs as a Claude Code `PreToolUse` hook and **blocks or asks before destructive commands, secret access, protected-path writes and risky MCP calls**, before the tool runs.
|
|
4
|
+
|
|
5
|
+
It is a seatbelt, not a sandbox. See [What it is not](#what-it-is-not).
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
uv tool install damage-control-dev # or: pipx install damage-control-dev
|
|
9
|
+
damage-control-dev init # register the hooks, self-test them, record the install
|
|
10
|
+
damage-control-dev status # read-only drift report
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Supports **Claude Code on macOS and Linux**, Python 3.9 or newer. Nothing else is supported or planned.
|
|
14
|
+
|
|
15
|
+
## What it does
|
|
16
|
+
|
|
17
|
+
- Guards Bash, Edit, Write, Read and MCP tool calls through one policy ladder (allow, ask, block).
|
|
18
|
+
- Re-scans wrapped commands as commands in their own right: `ssh host "..."`, `sh -c`, database clients (`psql -c`, `mysql -e`), `git -c` keys that run programs.
|
|
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
|
+
- 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
|
+
- 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 (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
|
+
- Offline: after `init` nothing touches the network.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
damage-control-dev check "git push --force origin main" # what would the hooks decide?
|
|
27
|
+
damage-control-dev rules # list enforced rule ids
|
|
28
|
+
damage-control-dev packs # list packs; enabling is human-terminal only
|
|
29
|
+
damage-control-dev doctor --fix # self-test and repair
|
|
30
|
+
damage-control-dev uninstall # remove exactly our entries
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## What it is not
|
|
34
|
+
|
|
35
|
+
- Not a sandbox, and not a boundary against a determined adversary. A process running as the same OS user can edit anything this tool can.
|
|
36
|
+
- Only as strong as the harness's hook surface: commands you type yourself with `!`, tools that have no hook event, and a hook that times out are outside its reach.
|
|
37
|
+
- No network egress filtering, containment or resource limits.
|
|
38
|
+
- Expect false positives and false negatives. The policy is: ask, then learn.
|
|
39
|
+
|
|
40
|
+
See [docs/threat-model.md](docs/threat-model.md).
|
|
41
|
+
|
|
42
|
+
## Where things live
|
|
43
|
+
|
|
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
|
+
|
|
46
|
+
- `damagecontrol.yml`: the protected policy (kill switch, `packs:`, `disabled_groups:`)
|
|
47
|
+
- `damage-control/`, `tooldefs/`: your own rule overlays
|
|
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
|
+
- `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
|
|
51
|
+
|
|
52
|
+
A policy with no `packs:` key loads the default groups and no vendor packs; an unreadable policy loads everything.
|
|
53
|
+
|
|
54
|
+
If the environment breaks (for example a `uv tool install --force` interrupted mid-upgrade) and the hook cannot start, every tool call is blocked by design. Run `python3 ~/.damage-control/bin/damage-control-breakglass unregister` from a normal terminal, then reinstall and run `init` again.
|
|
55
|
+
|
|
56
|
+
## Docs
|
|
57
|
+
|
|
58
|
+
Start at [docs/index.md](docs/index.md).
|
|
59
|
+
|
|
60
|
+
- [docs/threat-model.md](docs/threat-model.md): what it protects against and the residuals
|
|
61
|
+
- [docs/coverage.md](docs/coverage.md): which tools are covered and how
|
|
62
|
+
- [docs/rule-authoring.md](docs/rule-authoring.md): writing rules, packs and overlays
|
|
63
|
+
- [docs/extending.md](docs/extending.md): profiles, the hook runtime, registration and repair
|
|
64
|
+
|
|
65
|
+
## Security
|
|
66
|
+
|
|
67
|
+
A bypass of a shipped rule that the threat model says should be caught is a vulnerability. Report it privately, see [SECURITY.md](SECURITY.md).
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
Apache-2.0. Contributions are signed off under the DCO, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
name = "damage-control-dev"
|
|
3
3
|
dynamic = ["version"]
|
|
4
4
|
description = "Damage control for AI coding agents: a PreToolUse guard that blocks or asks before destructive commands and protected-path writes (Claude Code)."
|
|
5
|
+
readme = "README.md"
|
|
5
6
|
requires-python = ">=3.9"
|
|
6
7
|
license = { text = "Apache-2.0" }
|
|
7
8
|
authors = [{ name = "dotdev", email = "dev@dotdev.dev" }]
|
|
8
|
-
keywords = ["ai", "agent", "claude-code", "safety", "hooks"]
|
|
9
|
+
keywords = ["ai", "agent", "claude-code", "safety", "hooks", "guardrails"]
|
|
9
10
|
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"License :: OSI Approved :: Apache Software License",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
10
14
|
"Operating System :: MacOS",
|
|
11
15
|
"Operating System :: POSIX :: Linux",
|
|
12
16
|
"Programming Language :: Python :: 3",
|
|
@@ -16,6 +20,12 @@ classifiers = [
|
|
|
16
20
|
# bundled rules ship as JSON, so a hook with no PyYAML still enforces every bundled rule.
|
|
17
21
|
dependencies = ["pyyaml>=6.0"]
|
|
18
22
|
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/dotdevdotdev/damage-control-dev"
|
|
25
|
+
Source = "https://github.com/dotdevdotdev/damage-control-dev"
|
|
26
|
+
Issues = "https://github.com/dotdevdotdev/damage-control-dev/issues"
|
|
27
|
+
Security = "https://github.com/dotdevdotdev/damage-control-dev/security/policy"
|
|
28
|
+
|
|
19
29
|
[project.scripts]
|
|
20
30
|
damage-control-dev = "damage_control.cli:main"
|
|
21
31
|
|
|
@@ -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
|
|
@@ -1500,7 +1500,7 @@ def _record_load_failure(
|
|
|
1500
1500
|
detail = getattr(exc, "problem", None) or str(exc) or type(exc).__name__
|
|
1501
1501
|
where = f"{path}:{line}" if line else str(path)
|
|
1502
1502
|
try:
|
|
1503
|
-
print(f"
|
|
1503
|
+
print(f"damage-control: SKIPPED {where}: {detail}", file=sys.stderr)
|
|
1504
1504
|
except Exception:
|
|
1505
1505
|
# A dead stderr must not turn a reporting line into a changed verdict.
|
|
1506
1506
|
pass
|
|
@@ -2782,7 +2782,7 @@ def unattended_grant_allows(
|
|
|
2782
2782
|
# to a looser layer — see parse_unattended_allow.
|
|
2783
2783
|
return False, (
|
|
2784
2784
|
f"rule '{rule_id}' is named in unattended_allow but its entry is malformed, "
|
|
2785
|
-
f"so it grants nothing
|
|
2785
|
+
f"so it grants nothing"
|
|
2786
2786
|
)
|
|
2787
2787
|
if any(not s for s in scopes):
|
|
2788
2788
|
return True, f"rule '{rule_id}' is granted unattended (unscoped)"
|
|
@@ -5594,11 +5594,11 @@ def _ansi_c_fold(text: str) -> str:
|
|
|
5594
5594
|
return _ANSI_C.sub(_dec, text)
|
|
5595
5595
|
|
|
5596
5596
|
|
|
5597
|
-
def _resolve_word(word: str, cwd: str) -> str:
|
|
5598
|
-
"""``word`` (already unquoted) made absolute when it names a
|
|
5597
|
+
def _resolve_word(word: str, cwd: str, guarded: Callable[[str], bool]) -> str:
|
|
5598
|
+
"""``word`` (already unquoted) made absolute when it names a ``guarded`` path under ``cwd``."""
|
|
5599
5599
|
opt = _OPT_VALUE.fullmatch(word)
|
|
5600
5600
|
if opt: # of=<file>, --output=<file>, --output-document=<file>, -o=<file>
|
|
5601
|
-
return opt.group(1) + _resolve_word(opt.group(2), cwd)
|
|
5601
|
+
return opt.group(1) + _resolve_word(opt.group(2), cwd, guarded)
|
|
5602
5602
|
if word.startswith("-"):
|
|
5603
5603
|
return word
|
|
5604
5604
|
if _PLAIN_WORD.fullmatch(word):
|
|
@@ -5606,22 +5606,16 @@ def _resolve_word(word: str, cwd: str) -> str:
|
|
|
5606
5606
|
if re.search(r"[*?\[]", word):
|
|
5607
5607
|
hits = _glob_hits(joined)
|
|
5608
5608
|
return hits[0] if hits else word
|
|
5609
|
-
return joined if
|
|
5609
|
+
return joined if guarded(joined) else word
|
|
5610
5610
|
|
|
5611
5611
|
def _sub(m: "re.Match[str]") -> str: # a word with quotes inside (python -c "open('x')")
|
|
5612
5612
|
cand = posixpath.normpath(posixpath.join(cwd, m.group(1)))
|
|
5613
|
-
return cand if
|
|
5613
|
+
return cand if guarded(cand) else m.group(0)
|
|
5614
5614
|
|
|
5615
5615
|
return _REL_WORD.sub(_sub, word)
|
|
5616
5616
|
|
|
5617
5617
|
|
|
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
|
-
"""
|
|
5618
|
+
def _absolutize_line(text: str, cwd: str, guarded: Callable[[str], bool]) -> str:
|
|
5625
5619
|
folded = _ansi_c_fold(text).replace("${PWD}", cwd).replace("$PWD", cwd)
|
|
5626
5620
|
try:
|
|
5627
5621
|
lex = shlex.shlex(folded, posix=True, punctuation_chars=True)
|
|
@@ -5629,14 +5623,26 @@ def _absolutize(text: str, cwd: str) -> str:
|
|
|
5629
5623
|
words = list(lex)
|
|
5630
5624
|
except ValueError:
|
|
5631
5625
|
return _REL_WORD.sub(
|
|
5632
|
-
lambda m: _resolve_word(m.group(1), cwd), folded
|
|
5626
|
+
lambda m: _resolve_word(m.group(1), cwd, guarded), folded
|
|
5633
5627
|
)
|
|
5634
|
-
resolved = [w if _PUNCT_WORD.fullmatch(w) else _resolve_word(w, cwd) for w in words]
|
|
5628
|
+
resolved = [w if _PUNCT_WORD.fullmatch(w) else _resolve_word(w, cwd, guarded) for w in words]
|
|
5635
5629
|
if resolved == words and folded == text:
|
|
5636
5630
|
return text
|
|
5637
5631
|
return " ".join(w if _PUNCT_WORD.fullmatch(w) else _quote_word(w) for w in resolved)
|
|
5638
5632
|
|
|
5639
5633
|
|
|
5634
|
+
def _absolutize(text: str, cwd: str, guarded: Callable[[str], bool] = is_protected_control_plane) -> str:
|
|
5635
|
+
"""Rewrite words in ``text`` that name a ``guarded`` path under ``cwd`` (default: the control plane).
|
|
5636
|
+
|
|
5637
|
+
The text is first folded through what the shell does to a word before the
|
|
5638
|
+
program sees it — ANSI-C ``$'…'``, ``$PWD``, adjacent quotes, backslashes —
|
|
5639
|
+
so the relative spelling reaches the same check the absolute one does.
|
|
5640
|
+
Each line is rewritten on its own: a newline separates commands, and
|
|
5641
|
+
flattening it would pull a later command's verb out of command position.
|
|
5642
|
+
"""
|
|
5643
|
+
return "\n".join(_absolutize_line(line, cwd, guarded) for line in text.split("\n"))
|
|
5644
|
+
|
|
5645
|
+
|
|
5640
5646
|
_ASSIGN_STMT = _rx(
|
|
5641
5647
|
r"(?:^|[;&|\n({]|\b(?:then|do|else))[ \t]*(?:(?:export|local|declare|readonly)[ \t]+)?(?P<n>[A-Za-z_]\w*)=(?P<v>(?:'[^']*'|\"[^\"]*\"|[^\s;&|()<>'\"])*)(?=[ \t]*(?:[;&|\n]|$))"
|
|
5642
5648
|
)
|
|
@@ -5938,6 +5944,19 @@ def _alias_target(path: str) -> Optional[str]:
|
|
|
5938
5944
|
return None
|
|
5939
5945
|
|
|
5940
5946
|
|
|
5947
|
+
def _alias_variants(rest: str, cwd: str) -> List[str]:
|
|
5948
|
+
"""``rest`` with each relative word that is an existing alias of a protected file spelled as that file."""
|
|
5949
|
+
out: List[str] = []
|
|
5950
|
+
for m in itertools.islice(_PATH_WORD.finditer(rest), _REALPATH_WORD_CAP):
|
|
5951
|
+
word = m.group(0)
|
|
5952
|
+
if word.startswith(("/", "~", "-")) or len(word) < 2:
|
|
5953
|
+
continue
|
|
5954
|
+
target = _alias_target(posixpath.normpath(posixpath.join(cwd, word)))
|
|
5955
|
+
if target:
|
|
5956
|
+
out.append(rest.replace(word, _quote_word(target)))
|
|
5957
|
+
return out
|
|
5958
|
+
|
|
5959
|
+
|
|
5941
5960
|
def _cwd_realpath_variants(command: str) -> List[str]:
|
|
5942
5961
|
"""``_realpath_variants`` for a relative word after a literal ``cd <dir> &&`` (#1065).
|
|
5943
5962
|
|
|
@@ -5945,16 +5964,49 @@ def _cwd_realpath_variants(command: str) -> List[str]:
|
|
|
5945
5964
|
``_cwd_variants`` only rewrites words that are lexically protected. The
|
|
5946
5965
|
aliased word is resolved against the same cwd and judged as what it is.
|
|
5947
5966
|
"""
|
|
5967
|
+
return [new for cwd, rest in _cwd_scopes(command)[0] for new in _alias_variants(rest, cwd)]
|
|
5968
|
+
|
|
5969
|
+
|
|
5970
|
+
def _guarded_path_test(config: Dict[str, Any]) -> Callable[[str], bool]:
|
|
5971
|
+
"""Whether an absolute path is guarded by ANY class: control plane, zero-access, read-only, no-delete."""
|
|
5972
|
+
patterns = [
|
|
5973
|
+
p
|
|
5974
|
+
for key in ("zeroAccessPaths", "readOnlyPaths", "noDeletePaths")
|
|
5975
|
+
for p in (config.get(key) or [])
|
|
5976
|
+
if isinstance(p, str)
|
|
5977
|
+
]
|
|
5978
|
+
return lambda path: is_protected_control_plane(path) or any(match_path(path, p) for p in patterns)
|
|
5979
|
+
|
|
5980
|
+
|
|
5981
|
+
def _before_first_cd(command: str) -> str:
|
|
5982
|
+
"""The text that runs in the payload cwd: everything before the first ``cd`` / ``pushd`` / ``popd``."""
|
|
5983
|
+
starts = [m.start() for rx in (_CD_RE, _POPD_RE) for m in itertools.islice(rx.finditer(command), 1)]
|
|
5984
|
+
return command[: min(starts)] if starts else command
|
|
5985
|
+
|
|
5986
|
+
|
|
5987
|
+
def _cwd_resolved_variants(command: str, cwd: Optional[str], config: Dict[str, Any]) -> List[str]:
|
|
5988
|
+
"""``command`` with each relative operand that names a guarded path spelled absolutely.
|
|
5989
|
+
|
|
5990
|
+
Relative operands are resolved against the payload ``cwd`` — and its
|
|
5991
|
+
``realpath``, when the cwd is itself a symlink — for the text that runs
|
|
5992
|
+
there, and against each literal ``cd <dir> &&`` / ``git -C`` / ``env -C``
|
|
5993
|
+
scope for the text that runs in it. The absolute spelling is then judged by
|
|
5994
|
+
the same rules as any other, so ``> .claude/settings.json`` from ``$HOME`` is
|
|
5995
|
+
``> ~/.claude/settings.json``. A missing cwd adds nothing: what a relative
|
|
5996
|
+
word means is then unknown, which the unverifiable-target verdict owns.
|
|
5997
|
+
"""
|
|
5998
|
+
scopes = list(_cwd_scopes(command)[0])
|
|
5999
|
+
if cwd and os.path.isabs(cwd):
|
|
6000
|
+
lead = _before_first_cd(command)
|
|
6001
|
+
scopes += [(base, lead) for base in dict.fromkeys((posixpath.normpath(cwd), os.path.realpath(cwd)))]
|
|
6002
|
+
guarded = _guarded_path_test(config)
|
|
5948
6003
|
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
|
|
6004
|
+
for base, text in scopes:
|
|
6005
|
+
new = _absolutize(text, base, guarded)
|
|
6006
|
+
if new != text:
|
|
6007
|
+
out.append(new)
|
|
6008
|
+
out.extend(_alias_variants(text, base))
|
|
6009
|
+
return list(dict.fromkeys(out))
|
|
5958
6010
|
|
|
5959
6011
|
|
|
5960
6012
|
def _cwd_variants(command: str) -> List[str]:
|
|
@@ -9920,7 +9972,7 @@ def audit_best_effort(log: Any) -> Any:
|
|
|
9920
9972
|
log(*args, **kwargs)
|
|
9921
9973
|
except Exception as e:
|
|
9922
9974
|
try:
|
|
9923
|
-
print(f"
|
|
9975
|
+
print(f"damage-control: audit log failed ({type(e).__name__}) — decision unchanged",
|
|
9924
9976
|
file=sys.stderr)
|
|
9925
9977
|
except Exception:
|
|
9926
9978
|
pass
|
|
@@ -10020,6 +10072,15 @@ def _decide_command(
|
|
|
10020
10072
|
outer one.
|
|
10021
10073
|
"""
|
|
10022
10074
|
verdict = _decide_rules(command, config, cwd)
|
|
10075
|
+
resolved = []
|
|
10076
|
+
if not verdict.get("protected"):
|
|
10077
|
+
# The control plane outranks the escape hatch and the kill switch, so a
|
|
10078
|
+
# relative spelling of it is judged before either can end the walk.
|
|
10079
|
+
for variant in _cwd_resolved_variants(command, cwd, config):
|
|
10080
|
+
inner = _decide_rules(variant, config)
|
|
10081
|
+
if inner.get("protected"):
|
|
10082
|
+
return dict(inner, command=command)
|
|
10083
|
+
resolved.append(inner)
|
|
10023
10084
|
if (
|
|
10024
10085
|
_depth >= _RESCAN_DEPTH
|
|
10025
10086
|
or verdict.get("protected")
|
|
@@ -10028,8 +10089,10 @@ def _decide_command(
|
|
|
10028
10089
|
or verdict["decision"] == "block"
|
|
10029
10090
|
):
|
|
10030
10091
|
return verdict
|
|
10031
|
-
for
|
|
10032
|
-
|
|
10092
|
+
for inner in itertools.chain(
|
|
10093
|
+
resolved,
|
|
10094
|
+
(_decide_command(hidden, config, cwd, _depth + 1) for hidden in _hidden_commands(command, rules=True)),
|
|
10095
|
+
):
|
|
10033
10096
|
if _DECISION_RANK[inner["decision"]] > _DECISION_RANK[verdict["decision"]] and not (
|
|
10034
10097
|
inner.get("escape") or inner.get("disabled")
|
|
10035
10098
|
):
|
|
@@ -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
|
"|---|---|---|---|---|---|",
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: damage-control-dev
|
|
3
|
-
Version: 0.1.0
|
|
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
|
-
Author-email: dotdev <dev@dotdev.dev>
|
|
6
|
-
License: Apache-2.0
|
|
7
|
-
License-File: LICENSE
|
|
8
|
-
License-File: NOTICE
|
|
9
|
-
Keywords: agent,ai,claude-code,hooks,safety
|
|
10
|
-
Classifier: Operating System :: MacOS
|
|
11
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
-
Requires-Python: >=3.9
|
|
15
|
-
Requires-Dist: pyyaml>=6.0
|
|
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.0 → damage_control_dev-0.1.2}/src/damage_control/rules/cloud-hosting.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/cloud-hosting.yaml
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/containers.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/containers.yaml
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/control-plane.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/control-plane.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/databases.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/databases.yaml
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/firebase.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/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.0 → damage_control_dev-0.1.2}/src/damage_control/rules/infrastructure.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/infrastructure.yaml
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/mcp-tools.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/mcp-tools.yaml
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/outbound.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/outbound.yaml
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/payloads.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/payloads.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/rules/registry.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/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.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/docker.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/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.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/kubectl.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/kubectl.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/terraform.json
RENAMED
|
File without changes
|
{damage_control_dev-0.1.0 → damage_control_dev-0.1.2}/src/damage_control/tooldefs/terraform.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|