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:
@@ -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, ...}
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
;;
|
;;
|
||||||
|
|||||||
Submodule projects/discord-bot updated: 56938bbf5a...bd6009c478
Submodule projects/msp-tools/guru-rmm updated: c924081c83...38971315f1
Reference in New Issue
Block a user