Home/Production/Security

Security

What the grid does with the data and the strings you give it, where it deliberately trusts you, and how to keep a licence key out of a browser bundle.

Zero external runtime dependencies. Both packages depend on nothing but tslib and Angular itself. CSV, Excel and Word export are written by hand rather than delegated to a third-party library. That is a deliberate supply-chain decision: there is no transitive dependency tree under this grid to audit or to be compromised.

Cell data is never markup

Row values render as text. There is no code path that takes a cell value and injects it as HTML, so a row containing <script> displays those characters and does nothing else. This holds for cell values, group labels, tooltips, exported files and the print document.

// Cell VALUES are never treated as markup. This renders as text, always:
{ key: 'note', name: 'Note', prop: 'note' }

// A cell TEMPLATE is your own Angular template, so it is sanitised like any other
// Angular binding — interpolation escapes, and [innerHTML] goes through DomSanitizer.
<ng-template #gcCell let-value>{{ value }}</ng-template>

If you supply a cell template, it is your own Angular template and Angular's normal rules apply — interpolation escapes, and anything you bind with [innerHTML] passes through Angular's sanitiser. The grid does not weaken that.

Icons are trusted markup, on purpose

The one place the grid deliberately bypasses Angular's sanitiser is icon strings. Raw SVG cannot survive sanitisation, and the icon API exists so you can pass raw SVG — so those strings are marked trusted and injected as-is. The sites are the empty state, the error state, the tree expander, and context-menu item icons.

The contract, stated plainly: an icon string is code you are writing, not data you are displaying. It must be a literal, or built only from values you control. Build one from user input and you have written an XSS hole into your own application — the grid will render it exactly as instructed.
// SAFE — a literal you wrote. This is what the icon API is for.
config = {
  icons: {
    empty: '<svg viewBox="0 0 24 24"><path d="M4 4h16v16H4z"/></svg>',
  },
};
// UNSAFE — never do this. The icon string is injected as trusted markup,
// so anything user-controlled inside it executes.
config = {
  icons: {
    empty: `<svg><use href="${row.userSuppliedIconUrl}" /></svg>`,  // ✘
  },
};

Nothing about this is unusual — every grid with a raw-SVG icon API works this way, and the alternative is not accepting SVG. What matters is that it is written down, so a code reviewer on your side knows which inputs are trusted and can check them.

What the grid never does

  • No eval, no new Function, no string-compiled expressions. Value getters, formatters and comparators are functions you pass; there is no expression language to inject into.
  • No network calls of its own. Every request comes from the GridDataSource you wrote, to the URL you chose.
  • No remote assets. No CDN fonts, no icon sprite fetch, no telemetry, no analytics — nothing leaves the page.
  • No storage without being asked. Column layout persistence writes to localStorage only when you set persistColumns, under the key you name.

Content Security Policy

The grid runs under a strict CSP. It needs no 'unsafe-eval' and no external origins:

# The grid needs no 'unsafe-eval' and no remote origins.
# It has zero external runtime dependencies and loads nothing at runtime.
Content-Security-Policy:
  default-src 'self';
  script-src  'self';
  img-src     'self' data:;
  style-src   'self' 'unsafe-inline';   # Angular component styles

style-src 'unsafe-inline' is Angular's requirement for component styles, not the grid's — if your application already runs Angular under a CSP, you already have whatever it needs. Exported files are generated in the browser and handed to the user as a data: download, which is why img-src data: appears above.

Licence keys

This section applies only to @gridcraft/enterprise. A licence key is not a secret in the cryptographic sense — it is signed and verified offline — but it identifies your organisation and should not be handed to everyone who loads your site.

For a server-rendered application, keep it on the server:

// app.config.server.ts — the key stays on the server and never reaches the browser
import { provideGridcraftEnterpriseServer } from '@gridcraft/enterprise/server';

export const serverConfig = {
  providers: [provideGridcraftEnterpriseServer()],   // reads GRIDCRAFT_LICENSE_KEY
};

Rather than:

// app.config.ts — a key here is compiled into the CLIENT bundle. Anyone can read it.
providers: [provideGridcraftEnterprise({ licenseKey: 'gc_live_…' })],  // ✘ for SSR apps

The grid will tell you if you get this wrong. Putting a key in the client bundle of an SSR application prints a console warning naming the file to move it out of — and that warning is one of the few that print in production builds as well as development, precisely because this is the mistake worth catching late.

A licence validation endpoint, if you configure one, must use HTTPS. Configuring a plain http:// URL prints a warning; it does not silently proceed as if the choice were fine.

Reporting a vulnerability

Report suspected vulnerabilities privately rather than in a public issue. Please include the version of both packages, your Angular version, and a minimal reproduction. We will confirm receipt, tell you whether we agree it is a vulnerability, and say what we intend to do about it.

Known, documented, and not a bug

  • Icon strings bypass the sanitiser. Documented above. It is the API's purpose; the mitigation is the contract, not a code change.
  • The watermark and licence check are not a security boundary. The enterprise package is minified, and minification is obfuscation, not protection. Licensing is a commercial agreement enforced by a signature check — it is not designed to withstand a determined attacker with the bundle in front of them, and we do not claim it is.
  • Exports run in the browser. A user can export exactly the rows they can already see. If certain columns must never leave your server, do not send them to the client — column-level access control is your application's job, not a grid setting.