Skip to content

Commit 2402977

Browse files
committed
add claude to main repo
1 parent e5e5bbb commit 2402977

12 files changed

Lines changed: 771 additions & 5 deletions

File tree

.claude/commands/memo.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
description: Save current task state to auto-memory, then promote reusable lessons to skills and trim memory.
3+
---
4+
5+
# Memo
6+
7+
Save a snapshot of current work to persistent memory, then clean up.
8+
9+
## Step 1 — Save current state
10+
11+
Write a concise summary of in-progress or recently completed work to the
12+
auto-memory `MEMORY.md` for this project. Include:
13+
14+
- What was done (feature, bug, refactor, area of code)
15+
- Current status (completed, blocked, in-progress)
16+
- Key decisions or outcomes worth remembering across conversations
17+
18+
Do not duplicate information already in skills, CLAUDE.md, or README-CLAUDE.md.
19+
20+
## Step 2 — Promote to skills
21+
22+
Review the memory file for items that represent **reusable patterns or
23+
lessons** — things that would help future sessions on this project. For
24+
each such item:
25+
26+
1. Identify which skill file it belongs in (or create a new one under
27+
`.claude/skills/<name>/SKILL.md`).
28+
2. Add it to the appropriate skill.
29+
3. Remove it from memory (it now lives in the skill).
30+
31+
Examples of promotable items:
32+
- A non-obvious convention specific to this project
33+
- A "foot-gun" pattern worth warning future-you about
34+
- A reusable recipe (test invocation, deploy command, debugging trick)
35+
36+
## Step 3 — Trim memory
37+
38+
Remove from memory anything that is:
39+
- Already captured in skills, CLAUDE.md, or README-CLAUDE.md
40+
- Too specific to a single completed task to be useful again
41+
- Stale or superseded by later work
42+
43+
Keep memory concise — ideally under 30 lines.

.claude/commands/pr-squash.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# PR Squash
2+
3+
Create a clean PR by grouping the current branch's commits into logical squashed
4+
commits on a new branch, then opening a pull request.
5+
6+
## Instructions
7+
8+
1. **Determine the base branch.** Use `$ARGUMENTS` if provided, otherwise detect
9+
the repo's default branch (`main` or `master`) via `gh repo view --json
10+
defaultBranchRef -q .defaultBranchRef.name`.
11+
12+
2. **Collect the commit history.** Run:
13+
```
14+
git log --oneline --reverse <base>..<current-branch>
15+
```
16+
These are the commits to be grouped.
17+
18+
3. **Analyse and group the commits.** Read the diffs for each commit
19+
(`git show --stat <sha>` and `git show <sha>` for ambiguous cases).
20+
Group commits into logical units:
21+
- Each group should represent one cohesive change (a feature, a fix, a
22+
refactor, a config change, etc.).
23+
- Iterative fix-up commits ("fix typo", "try again", "wip") belong with the
24+
feature they relate to.
25+
- Keep genuinely independent changes in separate groups.
26+
- Preserve chronological order between groups where possible.
27+
28+
4. **Decide: one PR or multiple PRs.** If the groups fall into distinct,
29+
unrelated topics (e.g. "developer tooling" vs "production feature"), plan
30+
to create **separate PRs** — one per topic. Each PR gets its own squash
31+
branch (`<current-branch>-squash-1`, `-squash-2`, etc.) and contains only
32+
the groups for that topic. Groups that are closely related (e.g. a feature
33+
and its config) stay in the same PR as separate squashed commits.
34+
35+
Rule of thumb: if a reviewer would reasonably want to merge one topic
36+
without the other, they belong in separate PRs.
37+
38+
5. **Present the grouping plan.** Show the user a numbered list like:
39+
```
40+
PR 1: "Devcontainer hardening and tooling"
41+
Group 1: "harden devcontainer and add Just task runner"
42+
- abc1234 add security settings
43+
- def5678 replace tox with just
44+
45+
PR 2: "Add Dex OIDC authentication"
46+
Group 2: "configure Dex and argocd-monitor"
47+
- jkl3456 add Dex config
48+
- mno7890 fix client secret
49+
- pqr1234 fix audience mismatch
50+
```
51+
If all groups are closely related, show a single PR with multiple groups.
52+
Ask the user to confirm or adjust before proceeding.
53+
54+
6. **Create squash branch(es).** Once approved, for each PR:
55+
```
56+
git checkout -b <branch-name> <base>
57+
```
58+
Use `<current-branch>-squash` for a single PR, or
59+
`<current-branch>-squash-<N>` (or a short descriptive suffix) for multiple.
60+
61+
7. **Cherry-pick and squash each group.** For each group in the PR:
62+
```
63+
git cherry-pick --no-commit <sha1> <sha2> ...
64+
git commit -m "<group message>"
65+
```
66+
Use a well-written conventional commit message for each group. Include a
67+
short body if the group contains non-obvious changes. Preserve any
68+
`Co-Authored-By` trailers from the original commits.
69+
70+
8. **Push and create the PR(s).**
71+
```
72+
git push -u origin <branch-name>
73+
```
74+
Create each PR with `gh pr create` targeting the base branch. The PR body
75+
should summarise its squashed commit group(s).
76+
77+
9. **Switch back** to the original branch so the user's working state is
78+
unchanged.
79+
80+
## Edge cases
81+
- If there are fewer than 3 commits, suggest the user just squash-merge
82+
directly instead — but proceed if they insist.
83+
- If cherry-pick conflicts arise, stop and inform the user rather than
84+
auto-resolving.
85+
- Never force-push or modify the original branch.
86+
- If a `-squash` branch already exists, ask the user before overwriting.
87+
- When merging a PR created by this command, use `gh pr merge --merge`
88+
(not `--squash`) to preserve the curated commit structure.

.claude/commands/verify-sandbox.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
---
2+
description: Verify Claude's mount-namespace sandbox is intact — env canaries, masked credentials, gitconfig bind, and the four VS Code IPC sockets from the Demmel writeup.
3+
---
4+
5+
# Verify sandbox
6+
7+
Run the full sandbox verification described in `README-CLAUDE.md` and
8+
report a PASS/FAIL table. The threat model these checks defend against
9+
is documented in:
10+
11+
- `README-CLAUDE.md` (this repo) — sections **What's locked down** and
12+
**Verifying the sandbox**.
13+
- Daniel Demmel, *Coding agents in secured VS Code dev containers*
14+
<https://www.danieldemmel.me/blog/coding-agents-in-secured-vscode-dev-containers>
15+
— describes the `vscode-ipc-*.sock`, `vscode-git-*.sock`,
16+
`vscode-ssh-auth-*.sock`, and `vscode-remote-containers-ipc-*.sock`
17+
bridges in `/tmp` that re-appear up to ~60s after window attach. Our
18+
defence is the private mount namespace set up by `just claude`, not a
19+
one-shot sweep.
20+
21+
## How to run
22+
23+
Execute every check below in a single Bash invocation where practical
24+
(parallel them when independent). For each item, report PASS or FAIL
25+
with a one-line reason. Do not skip a check because an earlier one
26+
failed — collect everything, then summarise.
27+
28+
If any check FAILs, end the report with: "Sandbox is leaking — do not
29+
trust `--dangerously-skip-permissions` until fixed. Open an issue
30+
against `gilesknap/python-copier-template`."
31+
32+
## Checks
33+
34+
### 1. Namespace markers
35+
36+
- `IS_SANDBOX` env var must be `1` (set by `claude-sandbox.sh` after
37+
`unshare -m`). If unset, Claude was not launched via `just claude`.
38+
- `IN_DEVCONTAINER` env var must be set.
39+
40+
### 2. Host bridge env vars (must all be unset)
41+
42+
`SSH_AUTH_SOCK`, `GIT_ASKPASS`, `VSCODE_GIT_IPC_HANDLE`,
43+
`VSCODE_GIT_ASKPASS_NODE`, `VSCODE_GIT_ASKPASS_MAIN`,
44+
`VSCODE_IPC_HOOK_CLI`, `BROWSER`.
45+
46+
### 3. SSH agent unreachable
47+
48+
`ssh-add -l` must fail with "Could not open a connection to your
49+
authentication agent." Anything that lists keys is a FAIL.
50+
51+
### 4. `/tmp` and `/run/user` are private tmpfs
52+
53+
- `mount | grep ' on /tmp '` must show a `tmpfs` entry (this confirms
54+
the mount namespace is active for `/tmp`).
55+
- `ls /tmp` must NOT contain any of the four Demmel sockets:
56+
`vscode-ipc-*.sock`, `vscode-git-*.sock`, `vscode-ssh-auth-*.sock`,
57+
`vscode-remote-containers-ipc-*.sock`. Glob each one explicitly.
58+
- `ls /run/user/*/` must NOT contain `vscode-*` entries.
59+
60+
### 5. Host credential dirs masked
61+
62+
Each of these must be empty or absent:
63+
`/root/.ssh`, `/root/.gnupg`, `/root/.aws`, `/root/.azure`,
64+
`/root/.gcloud`, `/root/.docker`, `/root/.netrc`.
65+
66+
A non-empty `/root/.ssh` (containing `id_*` or `authorized_keys`) is a
67+
critical FAIL — the host SSH keys are reachable.
68+
69+
### 6. Gitconfig bind-mount
70+
71+
- `mount | grep '/root/.gitconfig'` must show a bind mount (typically
72+
`fuse-overlayfs` or `bind` from `/etc/claude-gitconfig`).
73+
- `git config --global --list` must contain ONLY:
74+
- `user.name` / `user.email` (host identity, copied through),
75+
- `safe.directory=*`,
76+
- `url.https://github.com/.insteadof=git@github.com:`,
77+
- `url.https://gitlab.diamond.ac.uk/.insteadof=git@gitlab.diamond.ac.uk:`,
78+
- `credential.https://github.com.helper=` then `!/usr/bin/gh auth git-credential`,
79+
- `credential.https://gitlab.diamond.ac.uk.helper=` then `!/usr/local/bin/glab auth git-credential`.
80+
- Any other `credential.*.helper` (especially one pointing at
81+
`/tmp/vscode-remote-containers-*.js` or `/.vscode-server/...`) is a
82+
FAIL.
83+
- No system-scope helper: `git config --system --get credential.helper`
84+
must exit non-zero.
85+
86+
### 7. Credential source is gh, not a host bridge
87+
88+
`printf 'protocol=https\nhost=github.com\n\n' | git credential fill`
89+
must return a `password=` line. The token prefix tells you the source:
90+
91+
- `gho_…` or `github_pat_…` from `gh auth git-credential` → PASS.
92+
- Anything else (e.g. a token from a `vscode-git-*.sock` bridge) → FAIL.
93+
94+
Do NOT print the token. Redact with `sed 's/password=.*/password=<REDACTED>/'`.
95+
Skip this check (mark N/A, not FAIL) if `just gh-auth` has not been run
96+
for this repo — the README explicitly carves that out.
97+
98+
## Output format
99+
100+
Print a single table:
101+
102+
```
103+
CHECK STATUS DETAIL
104+
1. IS_SANDBOX=1 PASS/FAIL ...
105+
2. Host bridge env vars unset PASS/FAIL ...
106+
3. ssh-add -l fails PASS/FAIL ...
107+
4a. /tmp is tmpfs PASS/FAIL ...
108+
4b. No vscode-*.sock in /tmp PASS/FAIL ...
109+
4c. No vscode-* in /run/user PASS/FAIL ...
110+
5. Host credential dirs masked PASS/FAIL ...
111+
6a. /root/.gitconfig bind-mounted PASS/FAIL ...
112+
6b. Gitconfig contents are sandbox-only PASS/FAIL ...
113+
6c. No system-scope credential.helper PASS/FAIL ...
114+
7. git credential fill source is gh PASS/FAIL/N/A ...
115+
```
116+
117+
End with one line: `RESULT: SANDBOX OK` if every check is PASS or N/A,
118+
otherwise `RESULT: SANDBOX LEAKING — see failures above` and the issue
119+
pointer.

.claude/hooks/sandbox-check.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/bin/bash
2+
# UserPromptSubmit hook: verify the Claude sandbox is intact before
3+
# executing any prompt. Exit code 2 blocks the prompt and shows the
4+
# message to the user. See README-CLAUDE.md for the full sandbox model.
5+
6+
fail() { echo "BLOCKED: $1" >&2; exit 2; }
7+
8+
# Are we in the devcontainer at all?
9+
[ -n "${IN_DEVCONTAINER:-}" ] || \
10+
fail "not in the devcontainer (IN_DEVCONTAINER unset). Reopen the project in the devcontainer."
11+
12+
# IS_SANDBOX=1 is set by the inner `just claude` script after it sets up
13+
# the private mount namespace. If it's missing, Claude was launched
14+
# without the namespace and /tmp/vscode-*.sock host bridges are reachable.
15+
[ -n "${IS_SANDBOX:-}" ] || \
16+
fail "IS_SANDBOX unset — Claude was not launched via \"just claude\", so the mount-namespace sandbox is not active."
17+
18+
# Host SSH agent must not be reachable. remoteEnv blanks SSH_AUTH_SOCK and
19+
# `just claude` re-blanks it; if it is set, neither layer applied.
20+
[ -z "${SSH_AUTH_SOCK:-}" ] || \
21+
fail "SSH_AUTH_SOCK is set ($SSH_AUTH_SOCK) — host SSH agent is reachable. run \"just claude\" or rebuild the devcontainer."
22+
23+
# GIT_ASKPASS points at a script under /.vscode-server, which the
24+
# namespace does NOT mask. If the env var is non-empty AND the file is
25+
# reachable, claude-sandbox.sh's exec-line blank failed to apply.
26+
[ ! -e "${GIT_ASKPASS:-}" ] || \
27+
fail "GIT_ASKPASS script ($GIT_ASKPASS) is reachable — claude-sandbox.sh did not blank the env var. Rebuild the devcontainer or re-run \"just claude\"."
28+
29+
exit 0

.claude/settings.json

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Edit(/workspaces/**)",
5+
"Write(/workspaces/**)",
6+
"Read(/workspaces/**)",
7+
"Bash(*)"
8+
],
9+
"deny": [
10+
"Bash(git push --force *)",
11+
"Bash(git reset --hard*)",
12+
"Bash(ssh *)",
13+
"Bash(ssh-agent *)",
14+
"Bash(*ssh-agent*)",
15+
"Bash(scp *)",
16+
"Bash(rsync *)",
17+
"Bash(sftp *)",
18+
"Bash(telnet *)",
19+
"Bash(mail *)",
20+
"Bash(sendmail *)"
21+
],
22+
"additionalDirectories": [
23+
"/workspaces/**"
24+
]
25+
},
26+
"hooks": {
27+
"UserPromptSubmit": [
28+
{
29+
"hooks": [
30+
{
31+
"type": "command",
32+
"command": ".claude/hooks/sandbox-check.sh"
33+
}
34+
]
35+
}
36+
]
37+
}
38+
}

0 commit comments

Comments
 (0)