@codyswann/lisa 2.334.0 → 2.335.0

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.
Files changed (68) hide show
  1. package/all/copy-overwrite/scripts/lisa-enforcement-fallback.sh +32 -9
  2. package/all/copy-overwrite/scripts/lisa-hooks/sonar-secrets.sh +139 -0
  3. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  4. package/dist/core/upstream-evidence-manifest.js +12 -4
  5. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  6. package/package.json +1 -1
  7. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  8. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  9. package/plugins/lisa/.codex-plugin/skills/lisa-setup-sonar/SKILL.md +45 -0
  10. package/plugins/lisa/hooks/sonar-secrets.sh +139 -0
  11. package/plugins/lisa/skills/lisa-setup-sonar/SKILL.md +45 -0
  12. package/plugins/lisa-agy/plugin.json +1 -1
  13. package/plugins/lisa-agy/skills/lisa-setup-sonar/SKILL.md +45 -0
  14. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  15. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  16. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  17. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-copilot/hooks/sonar-secrets.sh +139 -0
  21. package/plugins/lisa-copilot/skills/lisa-setup-sonar/SKILL.md +45 -0
  22. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-cursor/hooks/sonar-secrets.sh +139 -0
  24. package/plugins/lisa-cursor/skills/lisa-setup-sonar/SKILL.md +45 -0
  25. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  27. package/plugins/lisa-expo-agy/plugin.json +1 -1
  28. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  33. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  37. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  38. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  42. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  43. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  47. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  48. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  52. package/plugins/lisa-rails-agy/plugin.json +1 -1
  53. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  57. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  58. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  62. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  63. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  65. package/plugins/src/base/hooks/sonar-secrets.sh +139 -0
  66. package/plugins/src/base/skills/lisa-setup-sonar/SKILL.md +45 -0
  67. package/scripts/build-plugins.sh +14 -0
  68. package/scripts/lisa-enforcement-fallback.sh +32 -9
package/package.json CHANGED
@@ -115,7 +115,7 @@
115
115
  "brace-expansion": ">=5.0.9"
116
116
  },
117
117
  "name": "@codyswann/lisa",
118
- "version": "2.334.0",
118
+ "version": "2.335.0",
119
119
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
120
120
  "main": "dist/index.js",
121
121
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.334.0",
3
+ "version": "2.335.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.334.0",
3
+ "version": "2.335.0",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -80,6 +80,51 @@ MCP config so it launches `sonar run mcp`, authenticated from the same env token
80
80
 
81
81
  Skip any agent whose CLI is not installed; never wire Kiro or Gemini.
82
82
 
83
+ ## 4a. Patch the generated shims — required, not optional
84
+
85
+ Each integrate writes a five-line shim that pipes the hook payload to `sonar hook
86
+ <event>` and passes the verdict through unchanged. That makes an
87
+ **unauthenticated CLI refuse every prompt and every file read**, with a reason
88
+ string that reads exactly like a real finding:
89
+
90
+ ```text
91
+ SonarQube secret scanning is inactive: not authenticated. Run 'sonar auth login'.
92
+ ```
93
+
94
+ The operator most likely to hit it is the one who just installed the CLI and has
95
+ not logged in — so their very first prompt is rejected, before the session can
96
+ say anything useful. Repoint every generated shim at Lisa's wrapper, which keeps
97
+ real findings blocking, resolves `SONARQUBE_CLI_TOKEN` through
98
+ `lisa-secrets-access` when the CLI reports it has none, and degrades the
99
+ still-unauthenticated case to a warning:
100
+
101
+ ```bash
102
+ if ! command -v sonar &> /dev/null; then
103
+ exit 0
104
+ fi
105
+ here="$(cd -- "$(dirname -- "$0")" && pwd)"
106
+ exec bash "${here}/<relative-path>/scripts/lisa-hooks/sonar-secrets.sh" <vendor-event>
107
+ ```
108
+
109
+ One wrapper serves every surface — it takes the vendor event name as its only
110
+ argument. Known shim locations and their events:
111
+
112
+ | Agent | Shim | Event |
113
+ | --- | --- | --- |
114
+ | Claude | `.claude/hooks/sonar-secrets/build-scripts/prompt-secrets.sh` | `claude-prompt-submit` |
115
+ | Claude | `.claude/hooks/sonar-secrets/build-scripts/pretool-secrets.sh` | `claude-pre-tool-use` |
116
+ | Codex | `.codex/hooks/sonar-secrets/build-scripts/prompt-secrets.sh` | `codex-prompt-submit` |
117
+ | Copilot | `.github/hooks/sonar-secrets/build-scripts/pretool-secrets.sh` | `copilot-pre-tool-use` |
118
+ | Antigravity | `.agents/sonar/hooks/pretool-secrets.sh` | `antigravity-pre-tool-use` |
119
+
120
+ Compute the relative path from each shim's own directory rather than assuming a
121
+ depth — Antigravity's is three levels up, the rest are four.
122
+
123
+ **Re-running an integrate overwrites the shim with the vendor's original**, so
124
+ re-apply this patch whenever you re-run step 4, and say so in the summary. Leave
125
+ a comment at the top of each patched shim naming the wrapper, so the next reader
126
+ does not mistake the delegation for drift.
127
+
83
128
  ## 5. Write non-secret policy config
84
129
 
85
130
  Merge only non-secret identifiers into committed `.lisa.config.json`, preserving
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Shared wrapper around `sonar hook <event>` for every agent surface.
4
+ #
5
+ # `sonar integrate <agent>` generates a five-line shim per surface: if `sonar` is
6
+ # on PATH, pipe the hook payload to it and let whatever comes back stand. That is
7
+ # correct for the thing the scanner exists to do — refusing a prompt or a file
8
+ # read that genuinely contains a credential — and wrong for the thing it also
9
+ # does, which is refusing *everything* when the CLI has no token yet:
10
+ #
11
+ # {"decision":"block","reason":"SonarQube secret scanning is inactive:
12
+ # not authenticated. Run 'sonar auth login'."}
13
+ #
14
+ # At the shim level that reply is indistinguishable from a real finding, so a
15
+ # workstation that had simply never run the browser login could not submit a
16
+ # prompt at all. Two things are wrong with that, and this wrapper fixes both.
17
+ #
18
+ # 1. The credential is usually already provisioned. A project that declares
19
+ # SONARQUBE_CLI_TOKEN under `secrets.require` has it in its configured
20
+ # provider — and the CLI reads only the environment and the OS keychain, so on
21
+ # a local surface (which deliberately materializes nothing to disk) the value
22
+ # is present and unreachable at the same time. We resolve it through
23
+ # lisa-secrets-access, the one sanctioned reader, and only when the CLI says
24
+ # it needs it.
25
+ #
26
+ # 2. An unauthenticated scanner is a degraded gate, not a breach. This hook is
27
+ # one of several overlapping layers: secret scanning at commit time and in CI
28
+ # does not depend on it. Blocking every prompt to protect a redundant layer
29
+ # trades a certain outage for a hypothetical leak — and the outage lands on
30
+ # exactly the person least able to diagnose it, since the block fires before
31
+ # their first prompt is ever seen.
32
+ #
33
+ # A real finding still blocks. Only the "inactive" reply is downgraded, and the
34
+ # distinction is drawn on the reason text the vendor emits, so a detection that
35
+ # arrives while unauthenticated is impossible by construction — in that state the
36
+ # scanner does not scan at all.
37
+ #
38
+ # Usage: sonar-secrets.sh <vendor-event-name> # hook payload on stdin
39
+ set -uo pipefail
40
+
41
+ event="${1:-}"
42
+ [ -n "$event" ] || exit 0
43
+
44
+ # Nothing installed, nothing to enforce. Matches the vendor shim's own guard.
45
+ command -v sonar >/dev/null 2>&1 || exit 0
46
+
47
+ # An explicit off switch, for bisecting a session that is misbehaving. There is
48
+ # deliberately no way to turn the *blocking* back on from the environment: the
49
+ # decision that a missing token must not stop work belongs in this file, where it
50
+ # is reviewable, not in whatever shell happened to launch the agent.
51
+ [ "${LISA_SONAR_HOOK:-on}" = "off" ] && exit 0
52
+
53
+ payload="$(cat)"
54
+
55
+ # The vendor signals its verdict as JSON on stdout and always exits 0, so the
56
+ # exit code carries no information and the reason text is the only channel.
57
+ inactive_marker='secret scanning is inactive'
58
+
59
+ run_scanner() {
60
+ printf '%s' "$payload" | sonar hook "$event" 2>/dev/null
61
+ }
62
+
63
+ is_inactive() {
64
+ case "$1" in
65
+ *"$inactive_marker"*) return 0 ;;
66
+ *) return 1 ;;
67
+ esac
68
+ }
69
+
70
+ # Run the resolver with a ceiling, because the provider call crosses the network
71
+ # and this sits in front of every prompt and every file read. `timeout` is not
72
+ # present on a stock macOS, so this is the portable equivalent: start it, poll,
73
+ # give up. Giving up is safe — the caller treats an empty value as "the provider
74
+ # had nothing", which lands on the warn path rather than a block.
75
+ resolve_secret() {
76
+ local resolver="$1" name="$2" out pid waited=0
77
+ out="$(mktemp)" || return 0
78
+ node "$resolver" get "$name" >"$out" 2>/dev/null &
79
+ pid=$!
80
+ while kill -0 "$pid" 2>/dev/null; do
81
+ [ "$waited" -ge 10 ] && { kill -9 "$pid" 2>/dev/null; break; }
82
+ sleep 1
83
+ waited=$((waited + 1))
84
+ done
85
+ wait "$pid" 2>/dev/null
86
+ cat "$out"
87
+ rm -f "$out"
88
+ }
89
+
90
+ out="$(run_scanner)"
91
+
92
+ if is_inactive "$out"; then
93
+ # Where lisa-secrets-access lives depends on how the project receives Lisa.
94
+ # Claude and Codex get it as an installed plugin, which is not part of a clone;
95
+ # the agent skill directories and node_modules are what a fresh checkout
96
+ # actually has. Same search order as the remote-env setup script, including the
97
+ # `plugins/` rung that exists only in the Lisa monorepo itself.
98
+ repo_root="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
99
+ resolver=""
100
+ for candidate in \
101
+ "$repo_root/.claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs" \
102
+ "$repo_root/.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs" \
103
+ "$repo_root/.codex/skills/lisa-secrets-access/scripts/resolve-secret.mjs" \
104
+ "$repo_root/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs" \
105
+ "$repo_root/node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs"; do
106
+ if [ -f "$candidate" ]; then
107
+ resolver="$candidate"
108
+ break
109
+ fi
110
+ done
111
+
112
+ if [ -n "$resolver" ] && command -v node >/dev/null 2>&1; then
113
+ token="$(resolve_secret "$resolver" SONARQUBE_CLI_TOKEN)"
114
+ if [ -n "$token" ]; then
115
+ # Exported into this process only. Writing it anywhere durable would create
116
+ # a second live copy of a credential whose single store is the provider —
117
+ # see the one-store rule in lisa-secrets-access.
118
+ export SONARQUBE_CLI_TOKEN="$token"
119
+ org="$(resolve_secret "$resolver" SONARQUBE_CLI_ORG)"
120
+ [ -n "$org" ] && export SONARQUBE_CLI_ORG="$org"
121
+ out="$(run_scanner)"
122
+ fi
123
+ unset token org
124
+ fi
125
+ fi
126
+
127
+ if is_inactive "$out"; then
128
+ # stderr on a zero exit is advisory in every agent's hook protocol: the user
129
+ # sees it, the operation proceeds.
130
+ echo "sonar: local secret scanning is inactive (no token in the environment," >&2
131
+ echo " the OS keychain, or the configured secrets provider). Continuing —" >&2
132
+ echo " commit-time and CI secret scanning still gate this repository." >&2
133
+ echo " To restore it: 'sonar auth login', or store SONARQUBE_CLI_TOKEN in" >&2
134
+ echo " the project's secrets provider." >&2
135
+ exit 0
136
+ fi
137
+
138
+ [ -n "$out" ] && printf '%s' "$out"
139
+ exit 0
@@ -80,6 +80,51 @@ MCP config so it launches `sonar run mcp`, authenticated from the same env token
80
80
 
81
81
  Skip any agent whose CLI is not installed; never wire Kiro or Gemini.
82
82
 
83
+ ## 4a. Patch the generated shims — required, not optional
84
+
85
+ Each integrate writes a five-line shim that pipes the hook payload to `sonar hook
86
+ <event>` and passes the verdict through unchanged. That makes an
87
+ **unauthenticated CLI refuse every prompt and every file read**, with a reason
88
+ string that reads exactly like a real finding:
89
+
90
+ ```text
91
+ SonarQube secret scanning is inactive: not authenticated. Run 'sonar auth login'.
92
+ ```
93
+
94
+ The operator most likely to hit it is the one who just installed the CLI and has
95
+ not logged in — so their very first prompt is rejected, before the session can
96
+ say anything useful. Repoint every generated shim at Lisa's wrapper, which keeps
97
+ real findings blocking, resolves `SONARQUBE_CLI_TOKEN` through
98
+ `lisa-secrets-access` when the CLI reports it has none, and degrades the
99
+ still-unauthenticated case to a warning:
100
+
101
+ ```bash
102
+ if ! command -v sonar &> /dev/null; then
103
+ exit 0
104
+ fi
105
+ here="$(cd -- "$(dirname -- "$0")" && pwd)"
106
+ exec bash "${here}/<relative-path>/scripts/lisa-hooks/sonar-secrets.sh" <vendor-event>
107
+ ```
108
+
109
+ One wrapper serves every surface — it takes the vendor event name as its only
110
+ argument. Known shim locations and their events:
111
+
112
+ | Agent | Shim | Event |
113
+ | --- | --- | --- |
114
+ | Claude | `.claude/hooks/sonar-secrets/build-scripts/prompt-secrets.sh` | `claude-prompt-submit` |
115
+ | Claude | `.claude/hooks/sonar-secrets/build-scripts/pretool-secrets.sh` | `claude-pre-tool-use` |
116
+ | Codex | `.codex/hooks/sonar-secrets/build-scripts/prompt-secrets.sh` | `codex-prompt-submit` |
117
+ | Copilot | `.github/hooks/sonar-secrets/build-scripts/pretool-secrets.sh` | `copilot-pre-tool-use` |
118
+ | Antigravity | `.agents/sonar/hooks/pretool-secrets.sh` | `antigravity-pre-tool-use` |
119
+
120
+ Compute the relative path from each shim's own directory rather than assuming a
121
+ depth — Antigravity's is three levels up, the rest are four.
122
+
123
+ **Re-running an integrate overwrites the shim with the vendor's original**, so
124
+ re-apply this patch whenever you re-run step 4, and say so in the summary. Leave
125
+ a comment at the top of each patched shim naming the wrapper, so the next reader
126
+ does not mistake the delegation for drift.
127
+
83
128
  ## 5. Write non-secret policy config
84
129
 
85
130
  Merge only non-secret identifiers into committed `.lisa.config.json`, preserving
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.334.0",
3
+ "version": "2.335.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -80,6 +80,51 @@ MCP config so it launches `sonar run mcp`, authenticated from the same env token
80
80
 
81
81
  Skip any agent whose CLI is not installed; never wire Kiro or Gemini.
82
82
 
83
+ ## 4a. Patch the generated shims — required, not optional
84
+
85
+ Each integrate writes a five-line shim that pipes the hook payload to `sonar hook
86
+ <event>` and passes the verdict through unchanged. That makes an
87
+ **unauthenticated CLI refuse every prompt and every file read**, with a reason
88
+ string that reads exactly like a real finding:
89
+
90
+ ```text
91
+ SonarQube secret scanning is inactive: not authenticated. Run 'sonar auth login'.
92
+ ```
93
+
94
+ The operator most likely to hit it is the one who just installed the CLI and has
95
+ not logged in — so their very first prompt is rejected, before the session can
96
+ say anything useful. Repoint every generated shim at Lisa's wrapper, which keeps
97
+ real findings blocking, resolves `SONARQUBE_CLI_TOKEN` through
98
+ `lisa-secrets-access` when the CLI reports it has none, and degrades the
99
+ still-unauthenticated case to a warning:
100
+
101
+ ```bash
102
+ if ! command -v sonar &> /dev/null; then
103
+ exit 0
104
+ fi
105
+ here="$(cd -- "$(dirname -- "$0")" && pwd)"
106
+ exec bash "${here}/<relative-path>/scripts/lisa-hooks/sonar-secrets.sh" <vendor-event>
107
+ ```
108
+
109
+ One wrapper serves every surface — it takes the vendor event name as its only
110
+ argument. Known shim locations and their events:
111
+
112
+ | Agent | Shim | Event |
113
+ | --- | --- | --- |
114
+ | Claude | `.claude/hooks/sonar-secrets/build-scripts/prompt-secrets.sh` | `claude-prompt-submit` |
115
+ | Claude | `.claude/hooks/sonar-secrets/build-scripts/pretool-secrets.sh` | `claude-pre-tool-use` |
116
+ | Codex | `.codex/hooks/sonar-secrets/build-scripts/prompt-secrets.sh` | `codex-prompt-submit` |
117
+ | Copilot | `.github/hooks/sonar-secrets/build-scripts/pretool-secrets.sh` | `copilot-pre-tool-use` |
118
+ | Antigravity | `.agents/sonar/hooks/pretool-secrets.sh` | `antigravity-pre-tool-use` |
119
+
120
+ Compute the relative path from each shim's own directory rather than assuming a
121
+ depth — Antigravity's is three levels up, the rest are four.
122
+
123
+ **Re-running an integrate overwrites the shim with the vendor's original**, so
124
+ re-apply this patch whenever you re-run step 4, and say so in the summary. Leave
125
+ a comment at the top of each patched shim naming the wrapper, so the next reader
126
+ does not mistake the delegation for drift.
127
+
83
128
  ## 5. Write non-secret policy config
84
129
 
85
130
  Merge only non-secret identifiers into committed `.lisa.config.json`, preserving
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.334.0",
3
+ "version": "2.335.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.334.0",
3
+ "version": "2.335.0",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.334.0",
3
+ "version": "2.335.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.334.0",
3
+ "version": "2.335.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.334.0",
3
+ "version": "2.335.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.334.0",
3
+ "version": "2.335.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Shared wrapper around `sonar hook <event>` for every agent surface.
4
+ #
5
+ # `sonar integrate <agent>` generates a five-line shim per surface: if `sonar` is
6
+ # on PATH, pipe the hook payload to it and let whatever comes back stand. That is
7
+ # correct for the thing the scanner exists to do — refusing a prompt or a file
8
+ # read that genuinely contains a credential — and wrong for the thing it also
9
+ # does, which is refusing *everything* when the CLI has no token yet:
10
+ #
11
+ # {"decision":"block","reason":"SonarQube secret scanning is inactive:
12
+ # not authenticated. Run 'sonar auth login'."}
13
+ #
14
+ # At the shim level that reply is indistinguishable from a real finding, so a
15
+ # workstation that had simply never run the browser login could not submit a
16
+ # prompt at all. Two things are wrong with that, and this wrapper fixes both.
17
+ #
18
+ # 1. The credential is usually already provisioned. A project that declares
19
+ # SONARQUBE_CLI_TOKEN under `secrets.require` has it in its configured
20
+ # provider — and the CLI reads only the environment and the OS keychain, so on
21
+ # a local surface (which deliberately materializes nothing to disk) the value
22
+ # is present and unreachable at the same time. We resolve it through
23
+ # lisa-secrets-access, the one sanctioned reader, and only when the CLI says
24
+ # it needs it.
25
+ #
26
+ # 2. An unauthenticated scanner is a degraded gate, not a breach. This hook is
27
+ # one of several overlapping layers: secret scanning at commit time and in CI
28
+ # does not depend on it. Blocking every prompt to protect a redundant layer
29
+ # trades a certain outage for a hypothetical leak — and the outage lands on
30
+ # exactly the person least able to diagnose it, since the block fires before
31
+ # their first prompt is ever seen.
32
+ #
33
+ # A real finding still blocks. Only the "inactive" reply is downgraded, and the
34
+ # distinction is drawn on the reason text the vendor emits, so a detection that
35
+ # arrives while unauthenticated is impossible by construction — in that state the
36
+ # scanner does not scan at all.
37
+ #
38
+ # Usage: sonar-secrets.sh <vendor-event-name> # hook payload on stdin
39
+ set -uo pipefail
40
+
41
+ event="${1:-}"
42
+ [ -n "$event" ] || exit 0
43
+
44
+ # Nothing installed, nothing to enforce. Matches the vendor shim's own guard.
45
+ command -v sonar >/dev/null 2>&1 || exit 0
46
+
47
+ # An explicit off switch, for bisecting a session that is misbehaving. There is
48
+ # deliberately no way to turn the *blocking* back on from the environment: the
49
+ # decision that a missing token must not stop work belongs in this file, where it
50
+ # is reviewable, not in whatever shell happened to launch the agent.
51
+ [ "${LISA_SONAR_HOOK:-on}" = "off" ] && exit 0
52
+
53
+ payload="$(cat)"
54
+
55
+ # The vendor signals its verdict as JSON on stdout and always exits 0, so the
56
+ # exit code carries no information and the reason text is the only channel.
57
+ inactive_marker='secret scanning is inactive'
58
+
59
+ run_scanner() {
60
+ printf '%s' "$payload" | sonar hook "$event" 2>/dev/null
61
+ }
62
+
63
+ is_inactive() {
64
+ case "$1" in
65
+ *"$inactive_marker"*) return 0 ;;
66
+ *) return 1 ;;
67
+ esac
68
+ }
69
+
70
+ # Run the resolver with a ceiling, because the provider call crosses the network
71
+ # and this sits in front of every prompt and every file read. `timeout` is not
72
+ # present on a stock macOS, so this is the portable equivalent: start it, poll,
73
+ # give up. Giving up is safe — the caller treats an empty value as "the provider
74
+ # had nothing", which lands on the warn path rather than a block.
75
+ resolve_secret() {
76
+ local resolver="$1" name="$2" out pid waited=0
77
+ out="$(mktemp)" || return 0
78
+ node "$resolver" get "$name" >"$out" 2>/dev/null &
79
+ pid=$!
80
+ while kill -0 "$pid" 2>/dev/null; do
81
+ [ "$waited" -ge 10 ] && { kill -9 "$pid" 2>/dev/null; break; }
82
+ sleep 1
83
+ waited=$((waited + 1))
84
+ done
85
+ wait "$pid" 2>/dev/null
86
+ cat "$out"
87
+ rm -f "$out"
88
+ }
89
+
90
+ out="$(run_scanner)"
91
+
92
+ if is_inactive "$out"; then
93
+ # Where lisa-secrets-access lives depends on how the project receives Lisa.
94
+ # Claude and Codex get it as an installed plugin, which is not part of a clone;
95
+ # the agent skill directories and node_modules are what a fresh checkout
96
+ # actually has. Same search order as the remote-env setup script, including the
97
+ # `plugins/` rung that exists only in the Lisa monorepo itself.
98
+ repo_root="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
99
+ resolver=""
100
+ for candidate in \
101
+ "$repo_root/.claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs" \
102
+ "$repo_root/.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs" \
103
+ "$repo_root/.codex/skills/lisa-secrets-access/scripts/resolve-secret.mjs" \
104
+ "$repo_root/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs" \
105
+ "$repo_root/node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs"; do
106
+ if [ -f "$candidate" ]; then
107
+ resolver="$candidate"
108
+ break
109
+ fi
110
+ done
111
+
112
+ if [ -n "$resolver" ] && command -v node >/dev/null 2>&1; then
113
+ token="$(resolve_secret "$resolver" SONARQUBE_CLI_TOKEN)"
114
+ if [ -n "$token" ]; then
115
+ # Exported into this process only. Writing it anywhere durable would create
116
+ # a second live copy of a credential whose single store is the provider —
117
+ # see the one-store rule in lisa-secrets-access.
118
+ export SONARQUBE_CLI_TOKEN="$token"
119
+ org="$(resolve_secret "$resolver" SONARQUBE_CLI_ORG)"
120
+ [ -n "$org" ] && export SONARQUBE_CLI_ORG="$org"
121
+ out="$(run_scanner)"
122
+ fi
123
+ unset token org
124
+ fi
125
+ fi
126
+
127
+ if is_inactive "$out"; then
128
+ # stderr on a zero exit is advisory in every agent's hook protocol: the user
129
+ # sees it, the operation proceeds.
130
+ echo "sonar: local secret scanning is inactive (no token in the environment," >&2
131
+ echo " the OS keychain, or the configured secrets provider). Continuing —" >&2
132
+ echo " commit-time and CI secret scanning still gate this repository." >&2
133
+ echo " To restore it: 'sonar auth login', or store SONARQUBE_CLI_TOKEN in" >&2
134
+ echo " the project's secrets provider." >&2
135
+ exit 0
136
+ fi
137
+
138
+ [ -n "$out" ] && printf '%s' "$out"
139
+ exit 0
@@ -80,6 +80,51 @@ MCP config so it launches `sonar run mcp`, authenticated from the same env token
80
80
 
81
81
  Skip any agent whose CLI is not installed; never wire Kiro or Gemini.
82
82
 
83
+ ## 4a. Patch the generated shims — required, not optional
84
+
85
+ Each integrate writes a five-line shim that pipes the hook payload to `sonar hook
86
+ <event>` and passes the verdict through unchanged. That makes an
87
+ **unauthenticated CLI refuse every prompt and every file read**, with a reason
88
+ string that reads exactly like a real finding:
89
+
90
+ ```text
91
+ SonarQube secret scanning is inactive: not authenticated. Run 'sonar auth login'.
92
+ ```
93
+
94
+ The operator most likely to hit it is the one who just installed the CLI and has
95
+ not logged in — so their very first prompt is rejected, before the session can
96
+ say anything useful. Repoint every generated shim at Lisa's wrapper, which keeps
97
+ real findings blocking, resolves `SONARQUBE_CLI_TOKEN` through
98
+ `lisa-secrets-access` when the CLI reports it has none, and degrades the
99
+ still-unauthenticated case to a warning:
100
+
101
+ ```bash
102
+ if ! command -v sonar &> /dev/null; then
103
+ exit 0
104
+ fi
105
+ here="$(cd -- "$(dirname -- "$0")" && pwd)"
106
+ exec bash "${here}/<relative-path>/scripts/lisa-hooks/sonar-secrets.sh" <vendor-event>
107
+ ```
108
+
109
+ One wrapper serves every surface — it takes the vendor event name as its only
110
+ argument. Known shim locations and their events:
111
+
112
+ | Agent | Shim | Event |
113
+ | --- | --- | --- |
114
+ | Claude | `.claude/hooks/sonar-secrets/build-scripts/prompt-secrets.sh` | `claude-prompt-submit` |
115
+ | Claude | `.claude/hooks/sonar-secrets/build-scripts/pretool-secrets.sh` | `claude-pre-tool-use` |
116
+ | Codex | `.codex/hooks/sonar-secrets/build-scripts/prompt-secrets.sh` | `codex-prompt-submit` |
117
+ | Copilot | `.github/hooks/sonar-secrets/build-scripts/pretool-secrets.sh` | `copilot-pre-tool-use` |
118
+ | Antigravity | `.agents/sonar/hooks/pretool-secrets.sh` | `antigravity-pre-tool-use` |
119
+
120
+ Compute the relative path from each shim's own directory rather than assuming a
121
+ depth — Antigravity's is three levels up, the rest are four.
122
+
123
+ **Re-running an integrate overwrites the shim with the vendor's original**, so
124
+ re-apply this patch whenever you re-run step 4, and say so in the summary. Leave
125
+ a comment at the top of each patched shim naming the wrapper, so the next reader
126
+ does not mistake the delegation for drift.
127
+
83
128
  ## 5. Write non-secret policy config
84
129
 
85
130
  Merge only non-secret identifiers into committed `.lisa.config.json`, preserving
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.334.0",
3
+ "version": "2.335.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"