fix: bug bounty session — 13 bugs fixed, 4 dupes closed, 19 tickets resolved

ClaudeTools harness/skill fixes:
- syncro.md: replace jq with grep for comment ID parsing (CT#7)
- coord.py: print full UUID in todo list, not truncated 8 chars (CT#9)
- discord-bot: widen attachment save exception catch (CT#4/#18)
- get-token.sh: search repo-local identity.json first, graceful vault_path
  fallback chain instead of hard exit (CT#2/#14)
- reset-password.sh: actionable PAA role remediation instructions when
  Graph self-removal fails (CT#3)
- gw-sitemanager.sh: remove [:5000] JSON truncation in net raw (CT#5/#17)
- vault.sh (D:/vault): allowlist-based redaction filter on cmd_get (CT#1)

GuruRMM fixes (submodule):
- Rollback watchdog: systemd-run --scope cgroup escape (RMM#123)
- Remove dead WatchdogEvent WS code path (RMM#119)
- Close stale tickets for already-fixed bugs (RMM#79, #80, #104)

Tracker cleanup: closed 4 duplicate tickets, reclassified RMM#105 bug->feature,
marked CT#6 blocked on vendor, deprecated stale vault entry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-20 09:55:27 -07:00
parent b05333e2e0
commit 2f9eab6234
7 changed files with 55 additions and 11 deletions

View File

@@ -629,7 +629,7 @@ COMMENT_RESP=$(curl -s -X POST "${BASE}/tickets/${ID}/comment?api_key=${API_KEY}
} }
JSON JSON
) )
COMMENT_ID=$(echo "$COMMENT_RESP" | jq -r '.comment.id') COMMENT_ID=$(echo "$COMMENT_RESP" | grep -o '"id":[0-9]*' | head -1 | grep -o '[0-9]*')
``` ```
- `hidden: true` = internal only (customer can't see) - `hidden: true` = internal only (customer can't see)
@@ -1275,7 +1275,7 @@ COMMENT_RESP=$(curl -s -X POST "${BASE}/tickets/${ID}/comment?api_key=${API_KEY}
{"subject": "Resolution", "body": "<body>", "hidden": false, "do_not_email": false} {"subject": "Resolution", "body": "<body>", "hidden": false, "do_not_email": false}
JSON JSON
) )
COMMENT_ID=$(echo "$COMMENT_RESP" | jq -r '.comment.id') COMMENT_ID=$(echo "$COMMENT_RESP" | grep -o '"id":[0-9]*' | head -1 | grep -o '[0-9]*')
# STOP if null: GET ticket, check .ticket.comments[] by subject # STOP if null: GET ticket, check .ticket.comments[] by subject
# 2. Add line item — response is FLAT: {"id": N, ...} # 2. Add line item — response is FLAT: {"id": N, ...}

View File

@@ -236,7 +236,7 @@ def c_todo_list(a):
extra += f" @{t['assigned_to_user']}" extra += f" @{t['assigned_to_user']}"
if t.get("project_key"): if t.get("project_key"):
extra += f" ({t['project_key']})" extra += f" ({t['project_key']})"
print(f" [{t.get('status'):<7}] {str(t.get('id',''))[:8]} {str(t.get('text',''))[:88]}{extra}") print(f" [{t.get('status'):<7}] {t.get('id','')} {str(t.get('text',''))[:88]}{extra}")
def c_todo_done(a): def c_todo_done(a):

View File

@@ -134,13 +134,22 @@ if [[ -f "$CACHE_FILE" ]] && [[ $(find "$CACHE_FILE" -mmin -55 2>/dev/null) ]];
fi fi
# Locate vault repo via .claude/identity.json (per-machine, gitignored). # Locate vault repo via .claude/identity.json (per-machine, gitignored).
# Falls back to VAULT_PATH env var if set. # Resolution order for identity.json:
# 1. Repo-local: <claudetools-root>/.claude/identity.json
# 2. Home dir: $HOME/.claude/identity.json
# Falls back to VAULT_ROOT_ENV / VAULT_DIR env vars or D:/vault default if vault_path missing.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLAUDETOOLS_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)" CLAUDETOOLS_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
IDENTITY_FILE="$CLAUDETOOLS_ROOT/.claude/identity.json" IDENTITY_FILE=""
for _candidate in "$CLAUDETOOLS_ROOT/.claude/identity.json" "$HOME/.claude/identity.json"; do
if [[ -f "$_candidate" ]]; then
IDENTITY_FILE="$_candidate"
break
fi
done
VAULT_ROOT="${VAULT_ROOT_ENV:-}" VAULT_ROOT="${VAULT_ROOT_ENV:-}"
if [[ -z "$VAULT_ROOT" && -f "$IDENTITY_FILE" ]]; then if [[ -z "$VAULT_ROOT" && -n "$IDENTITY_FILE" ]]; then
if command -v jq >/dev/null 2>&1; then if command -v jq >/dev/null 2>&1; then
VAULT_ROOT=$(jq -r '.vault_path // empty' "$IDENTITY_FILE" 2>/dev/null) VAULT_ROOT=$(jq -r '.vault_path // empty' "$IDENTITY_FILE" 2>/dev/null)
fi fi
@@ -153,7 +162,15 @@ if [[ -z "$VAULT_ROOT" && -f "$IDENTITY_FILE" ]]; then
done done
fi fi
fi fi
[[ -z "$VAULT_ROOT" ]] && { echo "ERROR: vault_path not set in $IDENTITY_FILE and VAULT_ROOT_ENV env var not set" >&2; exit 3; } # Fallback: VAULT_DIR env var, then sensible default (D:/vault).
# Note: VAULT_PATH is already used in this script for the sops file path within the vault.
if [[ -z "$VAULT_ROOT" ]]; then
VAULT_ROOT="${VAULT_DIR:-}"
fi
if [[ -z "$VAULT_ROOT" ]]; then
VAULT_ROOT="D:/vault"
echo "[WARNING] vault_path not in identity.json and no VAULT_ROOT_ENV/VAULT_DIR env var; defaulting to $VAULT_ROOT" >&2
fi
[[ ! -d "$VAULT_ROOT" ]] && { echo "ERROR: vault not found at $VAULT_ROOT (check vault_path in $IDENTITY_FILE)" >&2; exit 3; } [[ ! -d "$VAULT_ROOT" ]] && { echo "ERROR: vault not found at $VAULT_ROOT (check vault_path in $IDENTITY_FILE)" >&2; exit 3; }
SOPS_FILE="$VAULT_ROOT/$VAULT_PATH" SOPS_FILE="$VAULT_ROOT/$VAULT_PATH"

View File

@@ -92,7 +92,34 @@ fi
cleanup() { cleanup() {
if [[ -n "$CREATED_ASSIGNMENT" ]]; then if [[ -n "$CREATED_ASSIGNMENT" ]]; then
DC=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE "${GH[@]}" "$G/roleManagement/directory/roleAssignments/$CREATED_ASSIGNMENT") DC=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE "${GH[@]}" "$G/roleManagement/directory/roleAssignments/$CREATED_ASSIGNMENT")
if [[ "$DC" == "204" ]]; then echo "[info] removed JIT role assignment (de-elevated)"; else echo "[WARNING] failed to remove JIT role assignment $CREATED_ASSIGNMENT (HTTP $DC) - REMOVE MANUALLY" >&2; bash "$__ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "reset-password: failed to remove JIT Privileged Auth Admin role - standing privilege left behind, REMOVE MANUALLY" --context "tenant=$TENANT_ID assignment=$CREATED_ASSIGNMENT http=$DC" >/dev/null 2>&1 || true; fi if [[ "$DC" == "204" ]]; then
echo "[info] removed JIT role assignment (de-elevated)"
else
# Graph blocks an SP from removing its own active role assignment (self-removal).
# The Tenant Admin SP is the only principal with RoleManagement.ReadWrite.Directory,
# so no other SP in our app suite can remove it either. A Global Admin must do it.
echo "" >&2
echo "[WARNING] ================================================================" >&2
echo "[WARNING] STANDING PRIVILEGE LEFT BEHIND — MANUAL REMOVAL REQUIRED" >&2
echo "[WARNING] ================================================================" >&2
echo "[WARNING] The Tenant Admin service principal now holds Privileged" >&2
echo "[WARNING] Authentication Administrator in tenant $TENANT_ID." >&2
echo "[WARNING] Graph returned HTTP $DC on self-removal (an SP cannot remove" >&2
echo "[WARNING] its own active directory-role assignment)." >&2
echo "[WARNING]" >&2
echo "[WARNING] To remove via Graph (requires a Global Admin token or a" >&2
echo "[WARNING] different principal with RoleManagement.ReadWrite.Directory):" >&2
echo "[WARNING]" >&2
echo "[WARNING] DELETE $G/roleManagement/directory/roleAssignments/$CREATED_ASSIGNMENT" >&2
echo "[WARNING]" >&2
echo "[WARNING] To remove via Azure Portal:" >&2
echo "[WARNING] 1. Sign in as Global Admin at https://entra.microsoft.com" >&2
echo "[WARNING] 2. Roles and administrators > Privileged Authentication Administrator" >&2
echo "[WARNING] 3. Find 'ComputerGuru - Tenant Admin' (SP id: $SPID)" >&2
echo "[WARNING] 4. Remove the assignment" >&2
echo "[WARNING] ================================================================" >&2
bash "$__ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "reset-password: self-removal blocked — standing PAA role left on Tenant Admin SP (HTTP $DC). A Global Admin must DELETE roleAssignments/$CREATED_ASSIGNMENT" --context "tenant=$TENANT_ID assignment=$CREATED_ASSIGNMENT sp=$SPID http=$DC" >/dev/null 2>&1 || true
fi
fi fi
} }
trap cleanup EXIT trap cleanup EXIT

View File

@@ -260,7 +260,7 @@ for x in sorted(d,key=lambda a:(a.get('type',''),a.get('name') or '')):
" || exit 1 ;; " || exit 1 ;;
raw) raw)
[ -z "$RAWPATH" ] && { echo "usage: $SELF net <console> raw <path under /proxy/network, e.g. /integration/v1/sites>" >&2; exit 2; } [ -z "$RAWPATH" ] && { echo "usage: $SELF net <console> raw <path under /proxy/network, e.g. /integration/v1/sites>" >&2; exit 2; }
api "$CB$RAWPATH" | "$PY" -c "import json,sys; print(json.dumps(json.load(sys.stdin),indent=1)[:5000])" || exit 1 ;; api "$CB$RAWPATH" | "$PY" -c "import json,sys; print(json.dumps(json.load(sys.stdin),indent=1))" || exit 1 ;;
*) echo "[$SELF] net subcommand must be radios|clients|devices|raw" >&2; exit 2 ;; *) echo "[$SELF] net subcommand must be radios|clients|devices|raw" >&2; exit 2 ;;
esac esac
;; ;;