Repository: grafana/grafana
Pull request #104950: Canvas: Zoom to content
Author: login: Develer
Labels: name: area/panel/canvas
description: Issues related to canvas panel
Created: 2025-05-05T15:27:22Z | Merged: 2025-05-08T19:23:57Z

<!--

Thank you for sending a pull request! Here are some tips:

1. If this is your first time, please read our contribution guide at https://github.com/grafana/grafana/blob/main/CONTRIBUTING.md

2. Ensure you include and run the appropriate tests as part of your Pull Request.

3. In a new feature or configuration option, an update to the documentation is necessary. Everything related to the documentation is under the docs folder in the root of the repository.

4. If the Pull Request is a work in progress, make use of GitHub's "Draft PR" feature and mark it as such.

5. If you can not merge your Pull Request due to a merge conflict, Rebase it. This gets it in sync with the main branch.

6. Name your PR as "<FeatureArea>: Describe your change", e.g. Alerting: Prevent race condition. If it's a fix or feature relevant for the changelog describe the user impact in the title. The PR title is used to auto-generate the changelog for issues marked with the "add to changelog" label.

7. If your PR content should be added to the What's New document for the next major or minor release, add the **add to what's new** label to your PR. Note that you should add this label to the main PR that introduces the feature; do not add this label to smaller PRs for the feature.

-->

**What is this feature?**

This PR implements zoom to content functionality.

https://github.com/user-attachments/assets/99a47b73-5568-4946-81d5-d94c68096014



For detailed description, please visit [this issue description](https://github.com/grafana/grafana/issues/104395).

**Which issue(s) does this PR fix?**:

<!--

- Automatically closes linked issue when the Pull Request is merged.

Usage: "Fixes #<issue number>", or "Fixes (paste link of issue)"

-->

Fixes #104395 

**Special notes for your reviewer:**

Please check that:
- [ ] It works as expected from a user's perspective.
- [ ] If this is a pre-GA feature, it is behind a feature toggle.
- [ ] The docs are updated, and if this is a [notable improvement](https://grafana.com/docs/writers-toolkit/contribute/release-notes/#how-to-determine-if-content-belongs-in-whats-new), it's added to our [What's New](https://grafana.com/docs/writers-toolkit/contribute/release-notes/) doc.


Linked issue #104395: Canvas: Define Canvas Behavior on Pan & Zoom Toggle
### Context
With the introduction of a global coordinate system for Canvas pan and zoom, we need to clearly define the Canvas behavior when toggling pan & zoom ON and OFF. This includes removing the now-redundant Infinite Panning toggle and aligning the behavior with core use cases for a smoother UX.

### What needs to be done
- Remove the Infinite Panning toggle — infinite panning becomes part of the pan & zoom toggle behavior.
- Define behavior when Pan & Zoom is toggled ON:
    - Allow free panning and zooming without constraints.
- Define behavior when Pan & Zoom is toggled OFF (or when exiting edit mode):
    - Provide options (could be presented as radio buttons in the UI) to control what happens:
        1. Fit to screen (default option): Zoom and pan to fit all content into the visible canvas area.
        2. Reset zoom and position: Move the canvas to top-left (0,0) at default zoom.
        3. ~~Keep current view as is: Leave the canvas at the last panned and zoomed state (to be discussed if this option is desirable).~~
- On panel resize:
    - Respect the selected behavior from the options above and reapply it as needed.

### Open Questions / Discussion Points
- ~~Do we want to support “Keep current view” as a third option, or should we limit the choices to “Fit to screen” and “Reset zoom”?~~ (We're not gonna support this option right now);
- Should this behavior apply only on toggle OFF, or also when switching between edit and view modes?

Diff:
diff --git a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts
index 84c0c84408d30..bfe8426adc499 100644
--- a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts
+++ b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts
@@ -113,10 +113,6 @@ export const defaultCanvasElementOptions: Partial<CanvasElementOptions> = {
 };
 
 export interface Options {
-  /**
-   * Enable infinite pan
-   */
-  infinitePan: boolean;
   /**
    * Enable inline editing
    */
@@ -147,11 +143,15 @@ export interface Options {
    * Show all available element types
    */
   showAdvancedTypes: boolean;
+  /**
+   * Zoom to content
+   */
+  zoomToContent: boolean;
 }
 
 export const defaultOptions: Partial<Options> = {
-  infinitePan: true,
   inlineEditing: true,
   panZoom: true,
   showAdvancedTypes: true,
+  zoomToContent: true,
 };
diff --git a/public/app/features/canvas/runtime/SceneTransformWrapper.tsx b/public/app/features/canvas/runtime/SceneTransformWrapper.tsx
deleted file mode 100644
index 075197a5536c6..0000000000000
--- a/public/app/features/canvas/runtime/SceneTransformWrapper.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import * as React from 'react';
-import { TransformWrapper, TransformComponent, ReactZoomPanPinchRef } from 'react-zoom-pan-pinch';
-
-import { config } from '@grafana/runtime';
-
-import { Scene } from './scene';
-
-type SceneTransformWrapperProps = {
-  scene: Scene;
-  children: React.ReactNode;
-};
-
-export const SceneTransformWrapper = ({ scene, children: sceneDiv }: SceneTransformWrapperProps) => {
-  const onZoom = (zoomPanPinchRef: ReactZoomPanPinchRef) => {
-    const scale = zoomPanPinchRef.state.scale;
-    // scene.scale = scale;
-
-    if (scene.shouldInfinitePan) {
-      const isScaleZoomedOut = scale < 1;
-
-      if (isScaleZoomedOut) {
-        scene.updateSize(scene.width / scale, scene.height / scale);
-        scene.panel.forceUpdate();
-      }
-    }
-  };
-
-  const onZoomStop = (zoomPanPinchRef: ReactZoomPanPinchRef) => {
-    const scale = zoomPanPinchRef.state.scale;
-    // scene.scale = scale;
-    updateMoveable(scale);
-  };
-
-  const onTransformed = (
-    _: ReactZoomPanPinchRef,
-    state: {
-      scale: number;
-      positionX: number;
-      positionY: number;
-    }
-  ) => {
-    const scale = state.scale;
-    // scene.scale = scale;
-    updateMoveable(scale);
-  };
-
-  const updateMoveable = (scale: number) => {
-    if (scene.moveable && scale > 0) {
-      scene.moveable.zoom = 1 / scale;
-      if (scale === 1) {
-        scene.moveable.snappable = true;
-      } else {
-        scene.moveable.snappable = false;
-      }
-    }
-  };
-
-  const onPanning = (_: ReactZoomPanPinchRef, event: MouseEvent | TouchEvent) => {
-    if (scene.shouldInfinitePan && event instanceof MouseEvent) {
-      // Get deltaX and deltaY from pan event and add it to current canvas dimensions
-      let deltaX = event.movementX;
-      let deltaY = event.movementY;
-      if (deltaX > 0) {
-        deltaX = 0;
-      }
-      if (deltaY > 0) {
-        deltaY = 0;
-      }
-
-      // TODO: Consider bounding to the scene elements instead of allowing "infinite" panning
-      // TODO: Consider making scene grow in all directions vs just down to the right / bottom
-      scene.updateSize(scene.width - deltaX, scene.height - deltaY);
-      scene.panel.forceUpdate();
-    }
-  };
-
-  const onSceneContainerMouseDown = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
-    // If pan and zoom is disabled or context menu is visible, don't pan
-    if ((!scene.shouldPanZoom || scene.contextMenuVisible) && (e.button === 1 || (e.button === 2 && e.ctrlKey))) {
-      e.preventDefault();
-      e.stopPropagation();
-    }
-
-    // If context menu is hidden, ignore left mouse or non-ctrl right mouse for pan
-    if (!scene.contextMenuVisible && !scene.isPanelEditing && e.button === 2 && !e.ctrlKey) {
-      e.preventDefault();
-      e.stopPropagation();
-    }
-  };
-
-  // Set panel content overflow to hidden to prevent canvas content from overflowing
-  // scene.div?.parentElement?.parentElement?.parentElement?.parentElement?.setAttribute('style', `overflow: hidden`);
-
-  return (
-    <TransformWrapper
-      doubleClick={{ mode: 'reset' }}
-      // ref={scene.transformComponentRef}
-      onZoom={onZoom}
-      onZoomStop={onZoomStop}
-      onTransformed={onTransformed}
-      disabled={!config.featureToggles.canvasPanelPanZoom || !scene.shouldPanZoom}
-      panning={{ allowLeftClickPan: false }}
-      limitToBounds={!scene.shouldInfinitePan}
-      minScale={scene.shouldInfinitePan ? 0.1 : undefined}
-      onPanning={onPanning}
-    >
-      <TransformComponent>
-        {/* The <div> element has child elements that allow for mouse events, so we need to disable the linter rule */}
-        {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
-        <div onMouseDown={onSceneContainerMouseDown}>{sceneDiv}</div>
-      </TransformComponent>
-    </TransformWrapper>
-  );
-};
diff --git a/public/app/features/canvas/runtime/scene.tsx b/public/app/features/canvas/runtime/scene.tsx
index ba12e687aecb6..5234075f650ee 100644
--- a/public/app/features/canvas/runtime/scene.tsx
+++ b/public/app/features/canvas/runtime/scene.tsx
@@ -79,7 +79,7 @@ export class Scene {
   isEditingEnabled?: boolean;
   shouldShowAdvancedTypes?: boolean;
   shouldPanZoom?: boolean;
-  shouldInfinitePan?: boolean;
+  zoomToContent?: boolean;
   skipNextSelectionBroadcast = false;
   ignoreDataUpdate = false;
   panel: CanvasPanel;
@@ -120,11 +120,11 @@ export class Scene {
     enableEditing: boolean,
     showAdvancedTypes: boolean,
     panZoom: boolean,
-    infinitePan: boolean,
+    zoomToContent: boolean,
     public onSave: (cfg: CanvasFrameOptions) => void,
     panel: CanvasPanel
   ) {
-    this.root = this.load(cfg, enableEditing, showAdvancedTypes, panZoom, infinitePan);
+    this.root = this.load(cfg, enableEditing, showAdvancedTypes, panZoom, zoomToContent);
 
     this.subscription = this.editModeEnabled.subscribe((open) => {
       if (!this.moveable || !this.isEditingEnabled) {
@@ -162,7 +162,7 @@ export class Scene {
     enableEditing: boolean,
     showAdvancedTypes: boolean,
     panZoom: boolean,
-    infinitePan: boolean
+    zoomToContent: boolean
   ) {
     this.root = new RootElement(
       cfg ?? {
@@ -176,11 +176,11 @@ export class Scene {
     this.isEditingEnabled = enableEditing;
     this.shouldShowAdvancedTypes = showAdvancedTypes;
     this.shouldPanZoom = panZoom;
-    this.shouldInfinitePan = infinitePan;
+    this.zoomToContent = zoomToContent;
 
     setTimeout(() => {
       // if (this.div) {
-      if (this.viewportDiv) {
+      if (this.viewportDiv && this.viewerDiv) {
         // If editing is enabled, clear selecto instance
         const destroySelecto = enableEditing;
         initMoveable(destroySelecto, enableEditing, this);
@@ -190,6 +190,7 @@ export class Scene {
         this.connections.updateState();
         // update initial connections svg size
         this.updateConnectionsSize();
+        this.fitContent(this, zoomToContent);
       }
     });
     return this.root;
@@ -220,6 +221,7 @@ export class Scene {
     }
 
     this.updateConnectionsSize();
+    this.fitContent(this, this.zoomToContent!);
   }
 
   updateConnectionsSize() {
@@ -327,6 +329,83 @@ export class Scene {
     }
   };
 
+  fitContent = (scene: Scene, zoomToContent: boolean) => {
+    const { root, viewerDiv, infiniteViewer } = scene;
+    if (zoomToContent && root.div && infiniteViewer && viewerDiv) {
+      const dimentions = this.calculateZoomToFitScale(Array.from(root.div.children), viewerDiv);
+      const { scale, centerX, centerY } = dimentions;
+      infiniteViewer.setZoom(scale);
+      infiniteViewer.scrollTo(centerX, centerY);
+    }
+  };
+
+  calculateZoomToFitScale(elements: Element[], container: HTMLDivElement, paddingRatio = 0.05) {
+    const bounds = this.calculateGroupBoundingBox(elements);
+    const containerRect = container.getBoundingClientRect();
+    const containerWidth = containerRect.width;
+    const containerHeight = containerRect.height;
+
+    const paddedWidth = containerWidth * (1 - 2 * paddingRatio);
+    const paddedHeight = containerHeight * (1 - 2 * paddingRatio);
+
+    const scaleX = paddedWidth / bounds.width;
+    const scaleY = paddedHeight / bounds.height;
+
+    // Use the smaller one to fit both horizontally and vertically
+    const scale = Math.min(scaleX, scaleY);
+
+    // calculate value to move to center
+    const centerX = (bounds.centerX * scale - containerWidth / 2) / scale;
+    const centerY = (bounds.centerY * scale - containerHeight / 2) / scale;
+
+    return {
+      scale,
+      centerX,
+      centerY,
+    };
+  }
+
+  extractTranslateFromTransform(transform: string) {
+    const matrix = new DOMMatrix(transform);
+    return { x: matrix.m41, y: matrix.m42 }; // m41 = translateX, m42 = translateY
+  }
+
+  calculateGroupBoundingBox(elements: Element[]) {
+    let minX = Infinity,
+      minY = Infinity;
+    let maxX = -Infinity,
+      maxY = -Infinity;
+
+    for (const el of elements) {
+      const style = window.getComputedStyle(el);
+      const { x: tx, y: ty } = this.extractTranslateFromTransform(style.transform || '');
+
+      const width = parseFloat(style.width);
+      const height = parseFloat(style.height);
+
+      const left = tx;
+      const top = ty;
+      const right = tx + width;
+      const bottom = ty + height;
+
+      minX = Math.min(minX, left);
+      minY = Math.min(minY, top);
+      maxX = Math.max(maxX, right);
+      maxY = Math.max(maxY, bottom);
+    }
+
+    return {
+      left: minX,
+      top: minY,
+      right: maxX,
+      bottom: maxY,
+      width: maxX - minX,
+      height: maxY - minY,
+      centerX: (minX + maxX) / 2,
+      centerY: (minY + maxY) / 2,
+    };
+  }
+
   render() {
     const hasDataLinks = this.tooltip?.element?.getLinks && this.tooltip.element.getLinks({}).length > 0;
     const hasActions = this.tooltip?.element?.options.actions && this.tooltip.element.options.actions.length > 0;
@@ -357,22 +436,6 @@ export class Scene {
       </>
     );
 
-    // return (
-    //   <InfiniteViewer
-    //     className="viewer"
-    //     margin={0}
-    //     threshold={0}
-    //     rangeX={[0, 0]}
-    //     rangeY={[0, 0]}
-    //     onScroll={e => {
-    //     }}
-    //   >
-    //     <div className="viewport">
-    //       {sceneDiv}
-    //     </div>
-    //   </InfiniteViewer>
-    // )
-
     return config.featureToggles.canvasPanelPanZoom ? (
       <>
         {/* <SceneTransformWrapper scene={this}>{sceneDiv}</SceneTransformWrapper> */}
diff --git a/public/app/features/canvas/runtime/sceneAbleManagement.ts b/public/app/features/canvas/runtime/sceneAbleManagement.ts
index 4e95e0dae2ff6..57f7dc226abf3 100644
--- a/public/app/features/canvas/runtime/sceneAbleManagement.ts
+++ b/public/app/features/canvas/runtime/sceneAbleManagement.ts
@@ -410,8 +410,12 @@ export const initMoveable = (destroySelecto = false, allowChanges = true, scene:
   scene.infiniteViewer = new InfiniteViewer(scene.viewerDiv!, scene.viewportDiv!, {
     preventWheelClick: false,
     useAutoZoom: true,
-    useMouseDrag: true,
-    useWheelScroll: true,
+    // useMouseDrag: true,
+    // useWheelScroll: true,
+    useMouseDrag: scene.shouldPanZoom,
+    useWheelScroll: scene.shouldPanZoom,
+    displayHorizontalScroll: false,
+    displayVerticalScroll: false,
   });
 
   // Handles context menu activation
diff --git a/public/app/plugins/panel/canvas/CanvasPanel.tsx b/public/app/plugins/panel/canvas/CanvasPanel.tsx
index 10f43819839b8..53287fd3dd238 100644
--- a/public/app/plugins/panel/canvas/CanvasPanel.tsx
+++ b/public/app/plugins/panel/canvas/CanvasPanel.tsx
@@ -75,7 +75,7 @@ export class CanvasPanel extends Component<Props, State> {
       allowEditing,
       this.props.options.showAdvancedTypes,
       this.props.options.panZoom,
-      this.props.options.infinitePan,
+      this.props.options.zoomToContent,
       this.onUpdateScene,
       this
     );
@@ -238,13 +238,13 @@ export class CanvasPanel extends Component<Props, State> {
     const shouldShowAdvancedTypesSwitched =
       this.props.options.showAdvancedTypes !== nextProps.options.showAdvancedTypes;
     const panZoomSwitched = this.props.options.panZoom !== nextProps.options.panZoom;
-    const infinitePanSwitched = this.props.options.infinitePan !== nextProps.options.infinitePan;
+    const zoomToContentSwitched = this.props.options.zoomToContent !== nextProps.options.zoomToContent;
     if (
       this.needsReload ||
       inlineEditingSwitched ||
       shouldShowAdvancedTypesSwitched ||
       panZoomSwitched ||
-      infinitePanSwitched
+      zoomToContentSwitched
     ) {
       if (inlineEditingSwitched) {
         // Replace scene div to prevent selecto instance leaks
@@ -257,7 +257,7 @@ export class CanvasPanel extends Component<Props, State> {
         nextProps.options.inlineEditing,
         nextProps.options.showAdvancedTypes,
         nextProps.options.panZoom,
-        nextProps.options.infinitePan
+        nextProps.options.zoomToContent
       );
       this.scene.updateSize(nextProps.width, nextProps.height);
       this.scene.updateData(nextProps.data);
diff --git a/public/app/plugins/panel/canvas/module.tsx b/public/app/plugins/panel/canvas/module.tsx
index 011c9e7868276..b7a2c62d55ab3 100644
--- a/public/app/plugins/panel/canvas/module.tsx
+++ b/public/app/plugins/panel/canvas/module.tsx
@@ -40,12 +40,10 @@ export const addStandardCanvasEditorOptions = (builder: PanelOptionsEditorBuilde
     showIf: (opts) => config.featureToggles.canvasPanelPanZoom && opts.panZoom,
   });
   builder.addBooleanSwitch({
-    path: 'infinitePan',
-    name: 'Infinite panning',
-    description:
-      'Enable infinite panning - useful for expansive canvases. Warning: this is an experimental feature and currently only works well with elements that are top / left constrained',
+    path: 'zoomToContent',
+    name: 'Zoom to content',
+    description: 'Automatically zoom to fit content',
     defaultValue: false,
-    showIf: (opts) => config.featureToggles.canvasPanelPanZoom && opts.panZoom,
   });
 };
 
diff --git a/public/app/plugins/panel/canvas/panelcfg.cue b/public/app/plugins/panel/canvas/panelcfg.cue
index 6da7cebacc44e..41d66bb52237d 100644
--- a/public/app/plugins/panel/canvas/panelcfg.cue
+++ b/public/app/plugins/panel/canvas/panelcfg.cue
@@ -96,8 +96,8 @@ composableKinds: PanelCfg: {
 					showAdvancedTypes: bool | *true
 					// Enable pan and zoom
 					panZoom: bool | *true
-					// Enable infinite pan
-					infinitePan: bool | *true
+					// Zoom to content
+					zoomToContent: bool | *true
 					// The root element of canvas (frame), where all canvas elements are nested
 					// TODO: Figure out how to define a default value for this
 					root: {
diff --git a/public/app/plugins/panel/canvas/panelcfg.gen.ts b/public/app/plugins/panel/canvas/panelcfg.gen.ts
index 9706c886f6d84..5e2ebb07e81c7 100644
--- a/public/app/plugins/panel/canvas/panelcfg.gen.ts
+++ b/public/app/plugins/panel/canvas/panelcfg.gen.ts
@@ -111,10 +111,6 @@ export const defaultCanvasElementOptions: Partial<CanvasElementOptions> = {
 };
 
 export interface Options {
-  /**
-   * Enable infinite pan
-   */
-  infinitePan: boolean;
   /**
    * Enable inline editing
    */
@@ -145,11 +141,15 @@ export interface Options {
    * Show all available element types
    */
   showAdvancedTypes: boolean;
+  /**
+   * Zoom to content
+   */
+  zoomToContent: boolean;
 }
 
 export const defaultOptions: Partial<Options> = {
-  infinitePan: true,
   inlineEditing: true,
   panZoom: true,
   showAdvancedTypes: true,
+  zoomToContent: true,
 };


Reviews:
user: login: fastfrwrd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T16:11:29Z
user: login: Develer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T12:37:11Z
user: login: drew08t
body: LGTM, just be sure to add test coverage for the added functions! 🚀
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T16:56:29Z

Review comments:
diff_hunk: @@ -1,114 +0,0 @@
-import * as React from 'react';
-import { TransformWrapper, TransformComponent, ReactZoomPanPinchRef } from 'react-zoom-pan-pinch';
path: public/app/features/canvas/runtime/SceneTransformWrapper.tsx
user: login: fastfrwrd
body: in this PR, we seem like we could just get rid of the dependency on this package altogether.
https://github.com/search?q=repo%3Agrafana%2Fgrafana%20react-zoom-pan-pinch&type=code

The only other usage of the package is setting up a ref which I think was used in this wrapper, so I think it would make sense in this PR to also eliminate that usage (found in [the scene](https://github.com/grafana/grafana/blob/d095cb4e04adb1aec574b1e5b7c88adceae2dba1/public/app/features/canvas/runtime/scene.tsx#L128)) and drop the dependency. wdyt? 
created_at: 2025-05-07T16:11:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,114 +0,0 @@
-import * as React from 'react';
-import { TransformWrapper, TransformComponent, ReactZoomPanPinchRef } from 'react-zoom-pan-pinch';
path: public/app/features/canvas/runtime/SceneTransformWrapper.tsx
user: login: Develer
body: Good catch!
Yes, definitely, I'll add this to my clean-up task. I'll address it in a separate PR, I don't want to overload this one 🙂
created_at: 2025-05-08T12:37:11Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-05T15:33:30Z
body: :cry: zizmor failed with exit code 14.

<details>
<summary>Expand for full output</summary>

```
error[template-injection]: code injection via template expansion
  --> ./.github/actions/setup-grafana-bench/action.yml:35:7
   |
35 |       - name: Setup Bench
   |         ^^^^^^^^^^^^^^^^^ this step
36 |         shell: bash
37 |         env:
38 |           GH_TOKEN: ${{ steps.generate_token.outputs.token }}
39 | /       run: |
40 | |         git clone https://x-access-token:${GH_TOKEN}@github.com/grafana/grafana-bench.git ../grafana-bench
...  |
43 | |         git switch ${{ inputs.branch }}
44 | |         go install .
   | |_____________________^ inputs.branch may expand into attacker-controllable code
   |
   = note: audit confidence → Low

error[template-injection]: code injection via template expansion
  --> ./.github/actions/test-coverage-processor/action.yml:29:7
   |
29 |       - name: Process Go coverage output
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
30 |         shell: bash
31 | /       run: |
32 | |         # Ensure valid coverage file even if empty
...  |
35 | |           echo "mode: set" > ${{ inputs.coverage-file }}
36 | |         fi
   | |__________^ inputs.coverage-file may expand into attacker-controllable code
   |
   = note: audit confidence → Low

error[template-injection]: code injection via template expansion
  --> ./.github/actions/test-coverage-processor/action.yml:29:7
   |
29 |       - name: Process Go coverage output
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
30 |         shell: bash
31 | /       run: |
32 | |         # Ensure valid coverage file even if empty
...  |
35 | |           echo "mode: set" > ${{ inputs.coverage-file }}
36 | |         fi
   | |__________^ inputs.coverage-file may expand into attacker-controllable code
   |
   = note: audit confidence → Low

error[unpinned-uses]: unpinned action reference
  --> ./.github/actions/test-coverage-processor/action.yml:39:7
   |
39 |       uses: codecov/codecov-action@v5
   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/alerting-swagger-gen.yml:24:9
   |
24 |         uses: peter-evans/create-pull-request@v5
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[dangerous-triggers]: use of fundamentally insecure workflow trigger
 --> ./.github/workflows/auto-milestone.yml:2:1
  |
2 | / on:
3 | |   pull_request_target:
... |
7 | |       - closed
8 | |       - ready_for_review
  | |________________________^ pull_request_target is almost always used insecurely
  |
  = note: audit confidence → Medium

error[excessive-permissions]: overly broad permissions
  --> ./.github/workflows/backend-unit-tests.yml:22:3
   |
22 |   id-token: write
   |   ^^^^^^^^^^^^^^^ id-token: write is overly broad at the workflow level
   |
   = note: audit confidence → High

error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
  --> ./.github/workflows/backend-unit-tests.yml:3:1
   |
 3 | / on:
 4 | |   pull_request:
...  |
13 | |       - 'docs/**'
14 | |       - '**/*.md'
   | |_________________^ generally used when publishing artifacts generated at runtime
15 |
...
35 |         - name: Setup Go
36 |           uses: actions/setup-go@v5
   |           ^^^^^^^^^^^^^^^^^^^^^^^^^ cache enabled by default here
   |
   = note: audit confidence → Low

error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
  --> ./.github/workflows/backend-unit-tests.yml:3:1
   |
 3 | / on:
 4 | |   pull_request:
...  |
13 | |       - 'docs/**'
14 | |       - '**/*.md'
   | |_________________^ generally used when publishing artifacts generated at runtime
15 |
...
52 |         - name: Setup Go
53 |           uses: actions/setup-go@v5
   |           ^^^^^^^^^^^^^^^^^^^^^^^^^ cache enabled by default here
   |
   = note: audit confidence → Low

error[dangerous-triggers]: use of fundamentally insecure workflow trigger
 --> ./.github/workflows/backport.yml:2:1
  |
2 | / on:
3 | |   pull_request_target:
4 | |     types:
5 | |       - closed
6 | |       - labeled
  | |_______________^ pull_request_target is almost always used insecurely
  |
  = note: audit confidence → Medium

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/bump-version.yml:32:9
   |
32 |   ...   name: Push & Create PR
   |         ^^^^^^^^^^^^^^^^^^^^^^ this step
33 | / ...   run: |
34 | | ...     git config --local user.name "github-actions[bot]"
...  |
40 | | ...     git push
41 | | ...     gh pr create --dry-run=${{ inputs.dry_run }} -l "type/ci" -l "no-changelog" -B "${{ github.ref_name }}" --title "Release: Bump version to ${{ inputs.version }}" --body "Updated version to ${{ inputs.version }}"
   | |__________________________________________________________________________________________________________________________________________________________________________________________________________________________^ inputs.version may expand into attacker-controllable code
   |
   = note: audit confidence → Low

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/bump-version.yml:32:9
   |
32 |   ...   name: Push & Create PR
   |         ^^^^^^^^^^^^^^^^^^^^^^ this step
33 | / ...   run: |
34 | | ...     git config --local user.name "github-actions[bot]"
...  |
40 | | ...     git push
41 | | ...     gh pr create --dry-run=${{ inputs.dry_run }} -l "type/ci" -l "no-changelog" -B "${{ github.ref_name }}" --title "Release: Bump version to ${{ inputs.version }}" --body "Updated version to ${{ inputs.version }}"
   | |__________________________________________________________________________________________________________________________________________________________________________________________________________________________^ inputs.version may expand into attacker-controllable code
   |
   = note: audit confidence → Low

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/bump-version.yml:32:9
   |
32 |   ...   name: Push & Create PR
   |         ^^^^^^^^^^^^^^^^^^^^^^ this step
33 | / ...   run: |
34 | | ...     git config --local user.name "github-actions[bot]"
...  |
40 | | ...     git push
41 | | ...     gh pr create --dry-run=${{ inputs.dry_run }} -l "type/ci" -l "no-changelog" -B "${{ github.ref_name }}" --title "Release: Bump version to ${{ inputs.version }}" --body "Updated version to ${{ inputs.version }}"
   | |__________________________________________________________________________________________________________________________________________________________________________________________________________________________^ inputs.dry_run may expand into attacker-controllable code
   |
   = note: audit confidence → Low

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/bump-version.yml:32:9
   |
32 |   ...   name: Push & Create PR
   |         ^^^^^^^^^^^^^^^^^^^^^^ this step
33 | / ...   run: |
34 | | ...     git config --local user.name "github-actions[bot]"
...  |
40 | | ...     git push
41 | | ...     gh pr create --dry-run=${{ inputs.dry_run }} -l "type/ci" -l "no-changelog" -B "${{ github.ref_name }}" --title "Release: Bump version to ${{ inputs.version }}" --body "Updated version to ${{ inputs.version }}"
   | |__________________________________________________________________________________________________________________________________________________________________________________________________________________________^ github.ref_name may expand into attacker-controllable code
   |
   = note: audit confidence → High

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/bump-version.yml:32:9
   |
32 |   ...   name: Push & Create PR
   |         ^^^^^^^^^^^^^^^^^^^^^^ this step
33 | / ...   run: |
34 | | ...     git config --local user.name "github-actions[bot]"
...  |
40 | | ...     git push
41 | | ...     gh pr create --dry-run=${{ inputs.dry_run }} -l "type/ci" -l "no-changelog" -B "${{ github.ref_name }}" --title "Release: Bump version to ${{ inputs.version }}" --body "Updated version to ${{ inputs.version }}"
   | |__________________________________________________________________________________________________________________________________________________________________________________________________________________________^ inputs.version may expand into attacker-controllable code
   |
   = note: audit confidence → Low

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/bump-version.yml:32:9
   |
32 |   ...   name: Push & Create PR
   |         ^^^^^^^^^^^^^^^^^^^^^^ this step
33 | / ...   run: |
34 | | ...     git config --local user.name "github-actions[bot]"
...  |
40 | | ...     git push
41 | | ...     gh pr create --dry-run=${{ inputs.dry_run }} -l "type/ci" -l "no-changelog" -B "${{ github.ref_name }}" --title "Release: Bump version to ${{ inputs.version }}" --body "Updated version to ${{ inputs.version }}"
   | |__________________________________________________________________________________________________________________________________________________________________________________________________________________________^ inputs.version may expand into attacker-controllable code
   |
   = note: audit confidence → Low

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/changelog.yml:91:9
   |
91 |       - name: "Create branch"
   |         ^^^^^^^^^^^^^^^^^^^^^ this step
92 |         run: git checkout -b "changelog/${{ github.run_id }}/${{ inputs.version }}"
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ inputs.version may expand into attacker-controllable code
   |
   = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/changelog.yml:101:9
    |
101 |         - name: "Patch CHANGELOG.md"
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
102 | /         run: |
103 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
128 | |
129 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/changelog.yml:101:9
    |
101 |         - name: "Patch CHANGELOG.md"
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
102 | /         run: |
103 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
128 | |
129 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/changelog.yml:101:9
    |
101 |         - name: "Patch CHANGELOG.md"
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
102 | /         run: |
103 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
128 | |
129 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/changelog.yml:101:9
    |
101 |         - name: "Patch CHANGELOG.md"
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
102 | /         run: |
103 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
128 | |
129 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/changelog.yml:101:9
    |
101 |         - name: "Patch CHANGELOG.md"
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
102 | /         run: |
103 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
128 | |
129 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/changelog.yml:101:9
    |
101 |         - name: "Patch CHANGELOG.md"
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
102 | /         run: |
103 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
128 | |
129 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/changelog.yml:101:9
    |
101 |         - name: "Patch CHANGELOG.md"
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
102 | /         run: |
103 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
128 | |
129 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/changelog.yml:101:9
    |
101 |         - name: "Patch CHANGELOG.md"
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
102 | /         run: |
103 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
128 | |
129 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/changelog.yml:101:9
    |
101 |         - name: "Patch CHANGELOG.md"
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
102 | /         run: |
103 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
128 | |
129 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/changelog.yml:138:9
    |
138 |         - name: "Create changelog PR"
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
139 | /         run: >
140 | |           gh pr create \
...   |
145 | |             --title "Release: update changelog for ${{ inputs.version }}" \
146 | |             --body "Changelog changes for release ${{ inputs.version }}"
    | |________________________________________________________________________^ inputs.dry_run may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/changelog.yml:138:9
    |
138 |         - name: "Create changelog PR"
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
139 | /         run: >
140 | |           gh pr create \
...   |
145 | |             --title "Release: update changelog for ${{ inputs.version }}" \
146 | |             --body "Changelog changes for release ${{ inputs.version }}"
    | |________________________________________________________________________^ inputs.target may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/changelog.yml:138:9
    |
138 |         - name: "Create changelog PR"
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
139 | /         run: >
140 | |           gh pr create \
...   |
145 | |             --title "Release: update changelog for ${{ inputs.version }}" \
146 | |             --body "Changelog changes for release ${{ inputs.version }}"
    | |________________________________________________________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/changelog.yml:138:9
    |
138 |         - name: "Create changelog PR"
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
139 | /         run: >
140 | |           gh pr create \
...   |
145 | |             --title "Release: update changelog for ${{ inputs.version }}" \
146 | |             --body "Changelog changes for release ${{ inputs.version }}"
    | |________________________________________________________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/codeowners-validator.yml:14:9
   |
14 |         uses: mszostok/codeowners-validator@v0.7.4
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/codeql-analysis.yml:57:7
   |
57 |       uses: github/codeql-action/init@v3
   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/codeql-analysis.yml:72:7
   |
72 |       uses: github/codeql-action/analyze@v3
   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[excessive-permissions]: overly broad permissions
  --> ./.github/workflows/commands.yml:17:3
   |
17 |   id-token: write
   |   ^^^^^^^^^^^^^^^ id-token: write is overly broad at the workflow level
   |
   = note: audit confidence → High

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/core-plugins-build-and-release.yml:45:9
   |
45 |         - name: Verify inputs
   |           ^^^^^^^^^^^^^^^^^^^ this step
46 | /         run: |
47 | |           if [ -z ${{ inputs.plugin_id }} ]; then echo "Missing plugin ID"; exit 1; fi
   | |______________________________________________________________________________________^ inputs.plugin_id may expand into attacker-controllable code
   |
   = note: audit confidence → Low

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/core-plugins-build-and-release.yml:67:9
   |
67 |         - name: Find plugin directory
   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
68 |           shell: bash
69 |           id: get_dir
70 | /         run: |
71 | |           dir=$(dirname \
...  |
76 | |           )
77 | |           echo "dir=${dir}" >> $GITHUB_OUTPUT
   | |_____________________________________________^ inputs.plugin_id may expand into attacker-controllable code
   |
   = note: audit confidence → Low

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/core-plugins-build-and-release.yml:90:9
   |
90 |         - name: Check backend
   |           ^^^^^^^^^^^^^^^^^^^ this step
91 |           id: check_backend
92 |           shell: bash
93 | /         run: |
94 | |           if egrep -qr --include=main.go 'datasource.Manage\("${{ inputs.plugin_id }}"' pkg/tsdb; then
...  |
97 | |             echo "has_backend=false" >> $GITHUB_OUTPUT
98 | |           fi
   | |____________^ inputs.plugin_id may expand into attacker-controllable code
   |
   = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:145:9
    |
145 |         - name: build:frontend
    |           ^^^^^^^^^^^^^^^^^^^^ this step
146 |           shell: bash
147 |           id: build_frontend
148 | /         run: |
149 | |           command="plugin:build:commit"
...   |
155 | |           version=$(cat ${{ steps.get_dir.outputs.dir }}/dist/plugin.json | jq -r .info.version)
156 | |           echo "version=${version}" >> $GITHUB_OUTPUT
    | |_____________________________________________________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:157:9
    |
157 |         - name: build:backend
    |           ^^^^^^^^^^^^^^^^^^^ this step
158 |           if: steps.check_backend.outputs.has_backend == 'true'
...
161 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
162 | /         run: |
163 | |           make build-plugin-go PLUGIN_ID=${{ inputs.plugin_id }}
    | |________________________________________________________________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:172:9
    |
172 |         - name: Check existing release
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
173 |           env:
174 |             GCOM_TOKEN: ${{ env.PLUGINS_GCOM_TOKEN }}
175 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
176 | /         run: |
177 | |           api_res=$(curl -X 'GET' -H "Authorization: Bearer $GCOM_TOKEN" \
...   |
186 | |             exit 1
187 | |           fi
    | |____________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:193:9
    |
193 |         - name: Publish release to Google Cloud Storage
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
194 |           working-directory: ${{ steps.get_dir.outputs.dir }}
195 |           env:
196 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
197 | /         run: |
198 | |           echo "Publish release to Google Cloud Storage:"
...   |
202 | |           gsutil -m cp -r ci/packages/*darwin* gs://${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/darwin
203 | |           gsutil -m cp -r ci/packages/*any* gs://${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/any
    | |_____________________________________________________________________________________________________________________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:193:9
    |
193 |         - name: Publish release to Google Cloud Storage
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
194 |           working-directory: ${{ steps.get_dir.outputs.dir }}
195 |           env:
196 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
197 | /         run: |
198 | |           echo "Publish release to Google Cloud Storage:"
...   |
202 | |           gsutil -m cp -r ci/packages/*darwin* gs://${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/darwin
203 | |           gsutil -m cp -r ci/packages/*any* gs://${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/any
    | |_____________________________________________________________________________________________________________________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:193:9
    |
193 |         - name: Publish release to Google Cloud Storage
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
194 |           working-directory: ${{ steps.get_dir.outputs.dir }}
195 |           env:
196 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
197 | /         run: |
198 | |           echo "Publish release to Google Cloud Storage:"
...   |
202 | |           gsutil -m cp -r ci/packages/*darwin* gs://${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/darwin
203 | |           gsutil -m cp -r ci/packages/*any* gs://${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/any
    | |_____________________________________________________________________________________________________________________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:193:9
    |
193 |         - name: Publish release to Google Cloud Storage
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
194 |           working-directory: ${{ steps.get_dir.outputs.dir }}
195 |           env:
196 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
197 | /         run: |
198 | |           echo "Publish release to Google Cloud Storage:"
...   |
202 | |           gsutil -m cp -r ci/packages/*darwin* gs://${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/darwin
203 | |           gsutil -m cp -r ci/packages/*any* gs://${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/any
    | |_____________________________________________________________________________________________________________________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:204:9
    |
204 |         - name: Publish new plugin version on grafana.com
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
205 |           if: steps.check_backend.outputs.has_backend == 'true'
...
209 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
210 | /         run: |
211 | |           echo "Publish new plugin version on grafana.com:"
...   |
245 | |             exit 1
246 | |           fi
    | |____________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:204:9
    |
204 |         - name: Publish new plugin version on grafana.com
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
205 |           if: steps.check_backend.outputs.has_backend == 'true'
...
209 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
210 | /         run: |
211 | |           echo "Publish new plugin version on grafana.com:"
...   |
245 | |             exit 1
246 | |           fi
    | |____________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:204:9
    |
204 |         - name: Publish new plugin version on grafana.com
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
205 |           if: steps.check_backend.outputs.has_backend == 'true'
...
209 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
210 | /         run: |
211 | |           echo "Publish new plugin version on grafana.com:"
...   |
245 | |             exit 1
246 | |           fi
    | |____________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:204:9
    |
204 |         - name: Publish new plugin version on grafana.com
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
205 |           if: steps.check_backend.outputs.has_backend == 'true'
...
209 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
210 | /         run: |
211 | |           echo "Publish new plugin version on grafana.com:"
...   |
245 | |             exit 1
246 | |           fi
    | |____________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:204:9
    |
204 |         - name: Publish new plugin version on grafana.com
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
205 |           if: steps.check_backend.outputs.has_backend == 'true'
...
209 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
210 | /         run: |
211 | |           echo "Publish new plugin version on grafana.com:"
...   |
245 | |             exit 1
246 | |           fi
    | |____________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:204:9
    |
204 |         - name: Publish new plugin version on grafana.com
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
205 |           if: steps.check_backend.outputs.has_backend == 'true'
...
209 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
210 | /         run: |
211 | |           echo "Publish new plugin version on grafana.com:"
...   |
245 | |             exit 1
246 | |           fi
    | |____________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:204:9
    |
204 |         - name: Publish new plugin version on grafana.com
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
205 |           if: steps.check_backend.outputs.has_backend == 'true'
...
209 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
210 | /         run: |
211 | |           echo "Publish new plugin version on grafana.com:"
...   |
245 | |             exit 1
246 | |           fi
    | |____________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:204:9
    |
204 |         - name: Publish new plugin version on grafana.com
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
205 |           if: steps.check_backend.outputs.has_backend == 'true'
...
209 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
210 | /         run: |
211 | |           echo "Publish new plugin version on grafana.com:"
...   |
245 | |             exit 1
246 | |           fi
    | |____________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:204:9
    |
204 |         - name: Publish new plugin version on grafana.com
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
205 |           if: steps.check_backend.outputs.has_backend == 'true'
...
209 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
210 | /         run: |
211 | |           echo "Publish new plugin version on grafana.com:"
...   |
245 | |             exit 1
246 | |           fi
    | |____________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:204:9
    |
204 |         - name: Publish new plugin version on grafana.com
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
205 |           if: steps.check_backend.outputs.has_backend == 'true'
...
209 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
210 | /         run: |
211 | |           echo "Publish new plugin version on grafana.com:"
...   |
245 | |             exit 1
246 | |           fi
    | |____________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:204:9
    |
204 |         - name: Publish new plugin version on grafana.com
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
205 |           if: steps.check_backend.outputs.has_backend == 'true'
...
209 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
210 | /         run: |
211 | |           echo "Publish new plugin version on grafana.com:"
...   |
245 | |             exit 1
246 | |           fi
    | |____________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:204:9
    |
204 |         - name: Publish new plugin version on grafana.com
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
205 |           if: steps.check_backend.outputs.has_backend == 'true'
...
209 |             VERSION: ${{ steps.build_frontend.outputs.version }}  
210 | /         run: |
211 | |           echo "Publish new plugin version on grafana.com:"
...   |
245 | |             exit 1
246 | |           fi
    | |____________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:247:9
    |
247 |         - name: Publish new plugin version on grafana.com (frontend only)
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
248 |           if: steps.check_backend.outputs.has_backend == 'false'
...
252 |             VERSION: ${{ steps.build_frontend.outputs.version }}
253 | /         run: |
254 | |           echo "Publish new plugin version on grafana.com:"
...   |
268 | |             exit 1
269 | |           fi
    | |_____________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/core-plugins-build-and-release.yml:247:9
    |
247 |         - name: Publish new plugin version on grafana.com (frontend only)
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
248 |           if: steps.check_backend.outputs.has_backend == 'false'
...
252 |             VERSION: ${{ steps.build_frontend.outputs.version }}
253 | /         run: |
254 | |           echo "Publish new plugin version on grafana.com:"
...   |
268 | |             exit 1
269 | |           fi
    | |_____________^ inputs.plugin_id may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/core-plugins-build-and-release.yml:57:9
   |
57 |         uses: 'google-github-actions/auth@v2'
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/core-plugins-build-and-release.yml:61:9
   |
61 |         uses: 'google-github-actions/setup-gcloud@v2'
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[excessive-permissions]: overly broad permissions
  --> ./.github/workflows/detect-breaking-changes-levitate.yml:11:3
   |
11 |   id-token: write
   |   ^^^^^^^^^^^^^^^ id-token: write is overly broad at the workflow level
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
   --> ./.github/workflows/detect-breaking-changes-levitate.yml:148:9
    |
148 |         uses: 'google-github-actions/auth@v2'
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
    |
    = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
   --> ./.github/workflows/detect-breaking-changes-levitate.yml:155:9
    |
155 |         uses: 'google-github-actions/setup-gcloud@v2'
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
    |
    = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
   --> ./.github/workflows/detect-breaking-changes-levitate.yml:241:9
    |
241 |         uses: marocchino/sticky-pull-request-comment@v2
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
    |
    = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
   --> ./.github/workflows/detect-breaking-changes-levitate.yml:258:9
    |
258 |         uses: marocchino/sticky-pull-request-comment@v2
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
    |
    = note: audit confidence → High

error[excessive-permissions]: overly broad permissions
  --> ./.github/workflows/frontend-lint.yml:11:3
   |
11 |   id-token: write
   |   ^^^^^^^^^^^^^^^ id-token: write is overly broad at the workflow level
   |
   = note: audit confidence → High

error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
  --> ./.github/workflows/frontend-lint.yml:2:1
   |
 2 | / on:
 3 | |   pull_request:
...  |
 6 | |       - main
 7 | |       - release-*.*.*
   | |_____________________^ generally used when publishing artifacts generated at runtime
 8 |
...
19 |       - uses: actions/setup-node@v4
20 | /       with:
21 | |         node-version-file: '.nvmrc'
22 | |         cache: 'yarn'
23 | |         cache-dependency-path: 'yarn.lock'
   | |__________________________________________^ opt-in for caching here
   |
   = note: audit confidence → Low

error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
  --> ./.github/workflows/frontend-lint.yml:2:1
   |
 2 | / on:
 3 | |   pull_request:
...  |
 6 | |       - main
 7 | |       - release-*.*.*
   | |_____________________^ generally used when publishing artifacts generated at runtime
 8 |
...
44 |       - uses: actions/setup-node@v4
45 | /       with:
46 | |         node-version-file: '.nvmrc'
47 | |         cache: 'yarn'
48 | |         cache-dependency-path: 'yarn.lock'
   | |__________________________________________^ opt-in for caching here
   |
   = note: audit confidence → Low

error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
  --> ./.github/workflows/frontend-lint.yml:2:1
   |
 2 | / on:
 3 | |   pull_request:
...  |
 6 | |       - main
 7 | |       - release-*.*.*
   | |_____________________^ generally used when publishing artifacts generated at runtime
 8 |
...
59 |       - uses: actions/setup-node@v4
60 | /       with:
61 | |         node-version-file: '.nvmrc'
62 | |         cache: 'yarn'
63 | |         cache-dependency-path: 'yarn.lock'
   | |__________________________________________^ opt-in for caching here
   |
   = note: audit confidence → Low

error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
  --> ./.github/workflows/frontend-lint.yml:2:1
   |
 2 | / on:
 3 | |   pull_request:
...  |
 6 | |       - main
 7 | |       - release-*.*.*
   | |_____________________^ generally used when publishing artifacts generated at runtime
 8 |
...
79 |       - uses: actions/setup-node@v4
80 | /       with:
81 | |         node-version-file: '.nvmrc'
82 | |         cache: 'yarn'
83 | |         cache-dependency-path: 'yarn.lock'
   | |__________________________________________^ opt-in for caching here
   |
   = note: audit confidence → Low

error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
  --> ./.github/workflows/frontend-lint.yml:2:1
   |
 2 | / on:
 3 | |   pull_request:
...  |
 6 | |       - main
 7 | |       - release-*.*.*
   | |_____________________^ generally used when publishing artifacts generated at runtime
 8 |
...
93 |       - uses: actions/setup-node@v4
94 | /       with:
95 | |         node-version-file: '.nvmrc'
96 | |         cache: 'yarn'
97 | |         cache-dependency-path: 'yarn.lock'
   | |__________________________________________^ opt-in for caching here
   |
   = note: audit confidence → Low

error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
   --> ./.github/workflows/frontend-lint.yml:2:1
    |
  2 | / on:
  3 | |   pull_request:
...   |
  6 | |       - main
  7 | |       - release-*.*.*
    | |_____________________^ generally used when publishing artifacts generated at runtime
  8 |
...
109 |       - uses: actions/setup-node@v4
110 | /       with:
111 | |         node-version-file: '.nvmrc'
112 | |         cache: 'yarn'
113 | |         cache-dependency-path: 'yarn.lock'
    | |__________________________________________^ opt-in for caching here
    |
    = note: audit confidence → Low

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/go-lint.yml:25:9
   |
25 |         uses: golangci/golangci-lint-action@v7
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/i18n-crowdin-download.yml:32:9
   |
32 |         uses: crowdin/github-action@v2
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/i18n-crowdin-download.yml:75:9
   |
75 |         uses: octokit/graphql-action@v2.x
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/i18n-crowdin-download.yml:95:9
   |
95 |         uses: octokit/graphql-action@v2.x
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/i18n-crowdin-upload.yml:20:9
   |
20 |         uses: crowdin/github-action@v2
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[excessive-permissions]: overly broad permissions
  --> ./.github/workflows/issue-opened.yml:15:3
   |
15 |   id-token: write
   |   ^^^^^^^^^^^^^^^ id-token: write is overly broad at the workflow level
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
   --> ./.github/workflows/issue-opened.yml:102:9
    |
102 |         uses: slackapi/slack-github-action@v1.27.0
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
    |
    = note: audit confidence → High

error[dangerous-triggers]: use of fundamentally insecure workflow trigger
  --> ./.github/workflows/pr-checks.yml:2:1
   |
 2 | / on:
 3 | |   pull_request_target:
...  |
15 | |       - milestoned
16 | |       - demilestoned
   | |____________________^ pull_request_target is almost always used insecurely
   |
   = note: audit confidence → Medium

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/pr-codeql-analysis-javascript.yml:31:7
   |
31 |       uses: github/codeql-action/init@v3
   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/pr-codeql-analysis-javascript.yml:36:7
   |
36 |       uses: github/codeql-action/analyze@v3
   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/pr-codeql-analysis-python.yml:29:7
   |
29 |       uses: github/codeql-action/init@v3
   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/pr-codeql-analysis-python.yml:34:7
   |
34 |       uses: github/codeql-action/analyze@v3
   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[dangerous-triggers]: use of fundamentally insecure workflow trigger
 --> ./.github/workflows/pr-commands.yml:2:1
  |
2 | / on:
3 | |   pull_request_target:
... |
6 | |       - opened
7 | |       - synchronize
  | |___________________^ pull_request_target is almost always used insecurely
  |
  = note: audit confidence → Medium

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/pr-e2e-tests.yml:29:9
   |
29 |       - uses: dagger/dagger-for-github@8.0.0
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[excessive-permissions]: overly broad permissions
  --> ./.github/workflows/pr-frontend-unit-tests.yml:11:3
   |
11 |   id-token: write
   |   ^^^^^^^^^^^^^^^ id-token: write is overly broad at the workflow level
   |
   = note: audit confidence → High

error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
  --> ./.github/workflows/pr-frontend-unit-tests.yml:2:1
   |
 2 | / on:
 3 | |   pull_request:
...  |
 6 | |       - main
 7 | |       - release-*.*.*
   | |_____________________^ generally used when publishing artifacts generated at runtime
 8 |
...
26 |       - uses: actions/setup-node@v4
27 | /       with:
28 | |         node-version-file: '.nvmrc'
29 | |         cache: 'yarn'
30 | |         cache-dependency-path: 'yarn.lock'
   | |__________________________________________^ opt-in for caching here
   |
   = note: audit confidence → Low

error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
  --> ./.github/workflows/pr-frontend-unit-tests.yml:2:1
   |
 2 | / on:
 3 | |   pull_request:
...  |
 6 | |       - main
 7 | |       - release-*.*.*
   | |_____________________^ generally used when publishing artifacts generated at runtime
 8 |
...
49 |       - uses: actions/setup-node@v4
50 | /       with:
51 | |         node-version-file: '.nvmrc'
52 | |         cache: 'yarn'
53 | |         cache-dependency-path: 'yarn.lock'
   | |__________________________________________^ opt-in for caching here
   |
   = note: audit confidence → Low

error[dangerous-triggers]: use of fundamentally insecure workflow trigger
  --> ./.github/workflows/pr-patch-check-event.yml:5:1
   |
 5 | / on:
 6 | |   pull_request_target:
...  |
16 | | # Since this is run on a pull request, we want to apply the patches intended for the
17 | | # target branch onto the source branch, to verify compatibility before merging.
   | |_______________________________________________________________________________^ pull_request_target is almost always used insecurely
   |
   = note: audit confidence → Medium

error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
  --> ./.github/workflows/pr-test-integration.yml:3:1
   |
 3 | / on:
 4 | |   push:
...  |
 7 | |       - release-*.*.*
 8 | |   pull_request:
   | |_______________^ generally used when publishing artifacts generated at runtime
 9 |
...
22 |           uses: actions/setup-go@v5
23 | /         with:
24 | |           go-version-file: go.mod
25 | |           cache: true
   | |_____________________^ opt-in for caching here
   |
   = note: audit confidence → Low

error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
  --> ./.github/workflows/pr-test-integration.yml:3:1
   |
 3 | / on:
 4 | |   push:
...  |
 7 | |       - release-*.*.*
 8 | |   pull_request:
   | |_______________^ generally used when publishing artifacts generated at runtime
 9 |
...
50 |           uses: actions/setup-go@v5
51 | /         with:
52 | |           go-version-file: go.mod
53 | |           cache: true
   | |_____________________^ opt-in for caching here
   |
   = note: audit confidence → Low

error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
  --> ./.github/workflows/pr-test-integration.yml:3:1
   |
 3 | / on:
 4 | |   push:
...  |
 7 | |       - release-*.*.*
 8 | |   pull_request:
   | |_______________^ generally used when publishing artifacts generated at runtime
 9 |
...
75 |           uses: actions/setup-go@v5
76 | /         with:
77 | |           go-version-file: go.mod
78 | |           cache: true
   | |_____________________^ opt-in for caching here
   |
   = note: audit confidence → Low

error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
  --> ./.github/workflows/publish-kinds-release.yml:3:1
   |
 3 | / on:
 4 | |   push:
...  |
10 | |       - '**/*.cue'
11 | |   workflow_dispatch:
   | |____________________^ generally used when publishing artifacts generated at runtime
12 |
...
39 |         - name: "Setup Go"
40 |           uses: "actions/setup-go@v4"
   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^ cache enabled by default here
   |
   = note: audit confidence → Low

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/release-comms.yml:36:7
   |
36 |        - if: ${{ github.event_name == 'workflow_dispatch' }}
   |  ________^
37 | |        run: |
   | | _______^
38 | ||         echo setting up GITHUB_ENV for ${{ github.event_name }}
39 | ||         echo "VERSION=${{ inputs.version }}" >> $GITHUB_ENV
40 | ||         echo "DRY_RUN=${{ inputs.dry_run }}" >> $GITHUB_ENV
41 | ||         echo "LATEST=${{ inputs.latest && '1' || '0' }}" >> $GITHUB_ENV
   | ||                                                                       ^
   | ||_______________________________________________________________________|
   |  |_______________________________________________________________________this step
   |                                                                          inputs.version may expand into attacker-controllable code
   |
   = note: audit confidence → Low

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/release-comms.yml:36:7
   |
36 |        - if: ${{ github.event_name == 'workflow_dispatch' }}
   |  ________^
37 | |        run: |
   | | _______^
38 | ||         echo setting up GITHUB_ENV for ${{ github.event_name }}
39 | ||         echo "VERSION=${{ inputs.version }}" >> $GITHUB_ENV
40 | ||         echo "DRY_RUN=${{ inputs.dry_run }}" >> $GITHUB_ENV
41 | ||         echo "LATEST=${{ inputs.latest && '1' || '0' }}" >> $GITHUB_ENV
   | ||                                                                       ^
   | ||_______________________________________________________________________|
   |  |_______________________________________________________________________this step
   |                                                                          inputs.dry_run may expand into attacker-controllable code
   |
   = note: audit confidence → Low

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/release-comms.yml:42:7
   |
42 |        - if: ${{ github.event.pull_request.merged == true && startsWith(github.head_ref, 'release/') }}
   |  ________^
43 | |        run: |
   | | _______^
44 | ||         echo "VERSION=$(echo ${{ github.head_ref }} | sed -e 's/release\/.*\//v/g')" >> $GITHUB_ENV
45 | ||         echo "DRY_RUN=${{ contains(github.event.pull_request.labels.*.name, 'release/dry-run') }}" >> $GITHUB_ENV
46 | ||         echo "LATEST=${{ contains(github.event.pull_request.labels.*.name, 'release/latest') && '1' || '0' }}" >> $GITHUB_ENV
   | ||                                                                                                                             ^
   | ||_____________________________________________________________________________________________________________________________|
   |  |_____________________________________________________________________________________________________________________________this step
   |                                                                                                                                github.head_ref may expand into attacker-controllable code
   |
   = note: audit confidence → High

error[excessive-permissions]: overly broad permissions
  --> ./.github/workflows/release-pr.yml:37:3
   |
37 |   contents: write
   |   ^^^^^^^^^^^^^^^ contents: write is overly broad at the workflow level
   |
   = note: audit confidence → High

error[excessive-permissions]: overly broad permissions
  --> ./.github/workflows/release-pr.yml:38:3
   |
38 |   pull-requests: write
   |   ^^^^^^^^^^^^^^^^^^^^ pull-requests: write is overly broad at the workflow level
   |
   = note: audit confidence → High

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/release-pr.yml:96:9
   |
96 |       - name: Create branch
   |         ^^^^^^^^^^^^^^^^^^^ this step
97 |         run: git checkout -b "release/${{ github.run_id }}/${{ inputs.version }}"
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ inputs.version may expand into attacker-controllable code
   |
   = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:105:9
    |
105 |         - name: Patch CHANGELOG.md
    |           ^^^^^^^^^^^^^^^^^^^^^^^^ this step
106 | /         run: |
107 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
134 | |
135 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:105:9
    |
105 |         - name: Patch CHANGELOG.md
    |           ^^^^^^^^^^^^^^^^^^^^^^^^ this step
106 | /         run: |
107 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
134 | |
135 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:105:9
    |
105 |         - name: Patch CHANGELOG.md
    |           ^^^^^^^^^^^^^^^^^^^^^^^^ this step
106 | /         run: |
107 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
134 | |
135 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:105:9
    |
105 |         - name: Patch CHANGELOG.md
    |           ^^^^^^^^^^^^^^^^^^^^^^^^ this step
106 | /         run: |
107 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
134 | |
135 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:105:9
    |
105 |         - name: Patch CHANGELOG.md
    |           ^^^^^^^^^^^^^^^^^^^^^^^^ this step
106 | /         run: |
107 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
134 | |
135 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:105:9
    |
105 |         - name: Patch CHANGELOG.md
    |           ^^^^^^^^^^^^^^^^^^^^^^^^ this step
106 | /         run: |
107 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
134 | |
135 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:105:9
    |
105 |         - name: Patch CHANGELOG.md
    |           ^^^^^^^^^^^^^^^^^^^^^^^^ this step
106 | /         run: |
107 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
134 | |
135 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:105:9
    |
105 |         - name: Patch CHANGELOG.md
    |           ^^^^^^^^^^^^^^^^^^^^^^^^ this step
106 | /         run: |
107 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
134 | |
135 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:105:9
    |
105 |         - name: Patch CHANGELOG.md
    |           ^^^^^^^^^^^^^^^^^^^^^^^^ this step
106 | /         run: |
107 | |           # Prepare CHANGELOG.md content with version delimiters
...   |
134 | |
135 | |           git diff CHANGELOG.md
    | |_______________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:146:9
    |
146 |         - name: Add package.json changes
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
147 | /         run: |
148 | |           git add package.json lerna.json yarn.lock packages public
149 | |           test -e e2e/test-plugins && git add e2e/test-plugins
150 | |           git commit -m "Update version to ${{ inputs.version }}"
    | |_________________________________________________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:152:9
    |
152 |       - name: Git push
    |         ^^^^^^^^^^^^^^ this step
153 |         if: ${{ inputs.dry_run }} != true
154 |         run: git push --set-upstream origin release/${{ github.run_id }}/${{ inputs.version }}
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:156:9
    |
156 |         - name: Create PR without backports
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
157 |           if: "${{ inputs.backport == '' }}"
158 | /         run: >
159 | |           gh pr create \
...   |
164 | |             --title "Release: ${{ inputs.version }}" \
165 | |             --body "These code changes must be merged after a release is complete"
    | |__________________________________________________________________________________^ inputs.latest may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:156:9
    |
156 |         - name: Create PR without backports
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
157 |           if: "${{ inputs.backport == '' }}"
158 | /         run: >
159 | |           gh pr create \
...   |
164 | |             --title "Release: ${{ inputs.version }}" \
165 | |             --body "These code changes must be merged after a release is complete"
    | |__________________________________________________________________________________^ inputs.dry_run may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:156:9
    |
156 |         - name: Create PR without backports
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
157 |           if: "${{ inputs.backport == '' }}"
158 | /         run: >
159 | |           gh pr create \
...   |
164 | |             --title "Release: ${{ inputs.version }}" \
165 | |             --body "These code changes must be merged after a release is complete"
    | |__________________________________________________________________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:169:9
    |
169 |         - name: Create PR with backports
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
170 |           if: "${{ inputs.backport != '' }}"
171 | /         run: >
172 | |           gh pr create \
...   |
178 | |             --title "Release: ${{ inputs.version }}" \
179 | |             --body "These code changes must be merged after a release is complete"
    | |__________________________________________________________________________________^ inputs.latest may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:169:9
    |
169 |         - name: Create PR with backports
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
170 |           if: "${{ inputs.backport != '' }}"
171 | /         run: >
172 | |           gh pr create \
...   |
178 | |             --title "Release: ${{ inputs.version }}" \
179 | |             --body "These code changes must be merged after a release is complete"
    | |__________________________________________________________________________________^ inputs.dry_run may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
   --> ./.github/workflows/release-pr.yml:169:9
    |
169 |         - name: Create PR with backports
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
170 |           if: "${{ inputs.backport != '' }}"
171 | /         run: >
172 | |           gh pr create \
...   |
178 | |             --title "Release: ${{ inputs.version }}" \
179 | |             --body "These code changes must be merged after a release is complete"
    | |__________________________________________________________________________________^ inputs.version may expand into attacker-controllable code
    |
    = note: audit confidence → Low

error[dangerous-triggers]: use of fundamentally insecure workflow trigger
 --> ./.github/workflows/run-dashboard-search-e2e.yml:3:1
  |
3 | / on:
4 | |   workflow_run:
... |
8 | |       - completed
9 | |   workflow_dispatch:
  | |____________________^ workflow_run is almost always used insecurely
  |
  = note: audit confidence → Medium

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/run-dashboard-search-e2e.yml:47:9
   |
47 |         uses: cypress-io/github-action@v6
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[github-env]: dangerous use of environment file
   --> ./.github/workflows/run-dashboard-search-e2e.yml:110:11
    |
110 | /           run: |
111 | |             FILE_NAME=$(basename "${{ matrix.ini_file }}" .ini)
112 | |             echo "FILE_NAME=$FILE_NAME" >> $GITHUB_ENV
    | |______________________________________________________^ write to GITHUB_ENV may allow code execution
    |
    = note: audit confidence → Low

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/run-e2e-suite.yml:26:9
   |
26 |       - run: echo "suite=$(echo ${{ inputs.suite }} | sed 's/\//-/g')" >> $GITHUB_ENV
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |         |
   |         this step
   |         inputs.suite may expand into attacker-controllable code
   |
   = note: audit confidence → Low

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/run-e2e-suite.yml:21:9
   |
21 |       - uses: dagger/dagger-for-github@8.0.0
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/run-schema-v2-e2e.yml:36:9
   |
36 |         uses: cypress-io/github-action@v6
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/skye-add-to-project.yml:51:9
   |
51 |         - name: Check if user is allowed
   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this step
52 |           id: check_user
53 | /         run: |
54 | |           # Convert the comma-separated list to an array
...  |
64 | |           done
65 | |           echo "user_allowed=false" >> $GITHUB_OUTPUT
   | |_____________________________________________________^ github.event.sender.login may expand into attacker-controllable code
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/skye-add-to-project.yml:71:9
   |
71 |         uses: octokit/graphql-action@v2.x
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/skye-add-to-project.yml:92:9
   |
92 |         uses: octokit/graphql-action@v2.x
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/storybook-verification.yml:36:9
   |
36 |         uses: cypress-io/github-action@v6
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[template-injection]: code injection via template expansion
  --> ./.github/workflows/sync-mirror-event.yml:26:9
   |
26 |          - uses: actions/github-script@v7
   |  __________^
27 | |          if: github.repository == 'grafana/grafana'
28 | |          with:
29 | |            github-token: ${{ steps.generate_token.outputs.token }}
30 | |            script: |
   | | ___________^
31 | ||             await github.rest.actions.createWorkflowDispatch({
...  ||
42 | ||                 }
43 | ||             })
   | ||               ^
   | ||_______________|
   |  |_______________this step
   |                  github.ref_name may expand into attacker-controllable code
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/trivy-scan.yml:21:7
   |
21 |       uses: aquasecurity/setup-trivy@v0.2.2
   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./.github/workflows/trivy-scan.yml:65:7
   |
65 |       uses: github/codeql-action/upload-sarif@v3
   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> ./pkg/build/actions/bump-version/action.yml:14:7
   |
14 |       uses: dagger/dagger-for-github@v5
   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

353 findings (160 ignored, 62 suppressed): 0 unknown, 0 informational, 0 low, 0 medium, 131 high
```
</details>
<!-- comment-action/zizmor GitHub Actions static analysis/analysis -->
author_association: CONTRIBUTOR
user: login: drew08t
created_at: 2025-05-08T16:57:57Z
body: One other thought, does the bounding calculations capture connections and custom vertices?
author_association: CONTRIBUTOR