Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ jobs:
- name: Install test-app dependencies
run: npm ci --prefix test-app

# Vitest transforms TS via esbuild for testing and does not run
# Angular's AOT compiler / template type-checking, so a change that
# breaks NgModule wiring or template types can pass lint + vitest while
# still failing a real build. `ng build` catches that class of error.
- name: Build test-app
run: npm --prefix test-app run build

# Guards against the security CI configuration (workflow, ZAP gate,
# exception baseline, docs) drifting out of the shape the QASP-driven
# gate depends on -- see docs/security-scanning.md and #614.
- name: Validate security workflow
run: npm run validate:security-workflow

# Guards against coverage-floor.json being lowered directly on this PR
# branch (paired with a coverage regression) rather than earned via
# `npm run coverage:bump`. Mirrors the ESLint baseline guard in
Expand Down
81 changes: 81 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Security

on:
pull_request:
push:
branches: [master]
workflow_dispatch:

# Least-privilege default. The DAST job runs PR-controlled code (checkout,
# npm scripts, the built test-app) and must never hold a write credential.
permissions:
contents: read

concurrency:
group: security-${{ github.ref }}
cancel-in-progress: true

jobs:
dast:
name: DAST (medium/high gate)
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .nvmrc
cache: npm
cache-dependency-path: |
package-lock.json
test-app/package-lock.json

# test-app compiles the library straight from the root `src/` tree, so
# root dependencies (Angular peers, rxjs, zone.js, etc.) must be
# present too -- same reasoning as e2e.yml.
- name: Install root dependencies
run: npm ci

- name: Install test-app dependencies
run: npm ci --prefix test-app

# test-app doubles as the DAST target: it renders library components
# via the routed gallery shell (AGENTS.md), the same real-browser
# surface the Playwright smoke suite exercises.
- name: Build test-app (production)
run: npm --prefix test-app run build -- --configuration production

- name: Serve test-app with production security headers
run: node scripts/serve-security-scan.mjs &

- name: Wait for test-app
run: |
for attempt in {1..30}; do
curl --fail --silent http://127.0.0.1:4200/ >/dev/null && exit 0
sleep 1
done
echo "test-app did not start within 30 seconds" >&2
exit 1

- name: Scan runtime with OWASP ZAP
uses: zaproxy/action-baseline@de8ad967d3548d44ef623df22cf95c3b0baf8b25 # v0.15.0
with:
target: "http://127.0.0.1:4200"
fail_action: false
allow_issue_writing: false
cmd_options: "-a"

- name: Enforce medium/high severity gate
run: node scripts/check-zap-severity.mjs report_json.json .zap/rules.tsv

- name: Upload ZAP report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: zap-report
path: report_*.html
if-no-files-found: warn
7 changes: 7 additions & 0 deletions .zap/rules.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# ZAP exceptions: rule-id<TAB>IGNORE<TAB>scope<TAB>issue-url<TAB>owner<TAB>expiry(YYYY-MM-DD)<TAB>rationale
# scope = a URL substring the exception is limited to (narrowest available scope), or * for rule-wide (must be justified).
# Low-risk alerts are reported but do not block the medium/high gate; only rows below are suppressed.
#
# Baselined from the first CI run on this PR (#614) -- see docs/security-scanning.md
# "Initial baseline and triage" for the triage process these follow.
10055 IGNORE * https://github.com/GSA/sam-ui-elements/issues/679 sam-ui-elements maintainers 2027-03-01 Angular's production build (styles.optimization.inlineCritical) inlines a critical-CSS <style> block into test-app's index.html at build time regardless of the inlineCritical setting once any component uses non-global styles; per-component inline styles (style bindings/attributes) further require style-src 'unsafe-inline' to render at all on this Angular version. This is test-app toolchain behavior, not a property of the shipped @gsa-sam/sam-ui-elements library. Revisit alongside the Angular upgrade in #679/#574 -- newer Angular supports nonce/hash-based inline styles.
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,23 @@ Two related traps worth knowing:
- `.github/workflows/lint.yml` additionally runs `scripts/check-baseline-not-increased.mjs`, comparing `eslint-baseline.json` on the PR branch against the base branch — this closes the hole where a contributor could raise the ceiling by hand-editing the JSON in the same PR that adds new warnings. The `--bump` scripts are the only sanctioned way to change either baseline file; same rule as `coverage-floor.json` above.
- Both `scripts/check-lint-baseline.test.mjs` and `scripts/check-baseline-not-increased.test.mjs` exist and pass but, like the coverage gate tests, are **not run in CI** — run `node --test scripts/*.test.mjs` manually when touching any gate script.

### Standalone-component lint policy (deferred)

`@angular-eslint/prefer-standalone` is **disabled** (not just downgraded to a warning) in both `eslint.config.mjs` (root) and `test-app/eslint.config.mjs`. This is a deliberate, evaluated decision (GSA/sam-ui-elements#584), not an oversight:

- Of the 190 flagged components (root) and 3 (`test-app`), all but one already have `standalone: false` and are registered via `declarations:` in one of 46 `NgModule`s across the library. The single `standalone: true` outlier (`SamAutocompleteComponent`) is still consumed through `declarations:` in two NgModules, so the flag doesn't reflect real usage either.
- Applying ESLint's own `--fix` for this rule strips `standalone: false`, flipping Angular's default to `standalone: true`. This was tried in #582/PR #675 and broke `TestBed.configureTestingModule` for every spec declaring one of these components — 827 failing tests. It is a behavior change, not a style fix, so it cannot be autofixed or done piecemeal without also rewriting every consuming `NgModule`'s `declarations:` to `imports:` and updating every affected spec's `TestBed` config (106+ specs use `declarations:`).
- Several of the affected files (e.g. `hierarchical.module`, `progress.module`, `autocomplete.module`, `progress.component`, `autocomplete-multiselect.component`, `radiobutton.component`, `text.component`, `toolbar.component`) are in the frozen `scripts/consumer-deep-imports.json` contract — an uncoordinated standalone migration risks a breaking change for downstream consumers, not just an internal refactor.
- **Decision: defer.** A real standalone migration is out of scope for lint-debt cleanup and would need its own epic, planned in consumer-safe slices (NgModule → `imports:` rewrites, spec updates, coordinated consumer rollout) rather than a mechanical lint fix. Until that epic exists, the rule stays off so it doesn't produce unactionable warnings.

### `@angular-eslint/prefer-inject` lint policy (deferred)

`@angular-eslint/prefer-inject` is **disabled** (not just downgraded to a warning) in the root `eslint.config.mjs`, following the same precedent as `@angular-eslint/prefer-standalone` above:

- angular-eslint 20's `tsRecommended` config newly includes this rule (as of the Angular 19→20 bump, #574), surfacing 142 constructor-DI findings that didn't exist in the prior baseline.
- Angular ships a mechanical fixer (`ng generate @angular/core:inject-migration`), but running it repo-wide rewrites classes under `src/ui-kit/experimental/patterns/layout/components/core/**` (e.g. `ScrollDispatcher`, `Scrollable`) that several specs instantiate directly via `new ScrollDispatcher(ngZone, platform)` rather than through Angular DI — the migrated `inject()` field initializers throw `NG0203` outside an injection context, breaking 170 tests.
- **Decision: defer.** Tracked in GSA/sam-ui-elements#710 (parented under the lint-debt epic #580) as consumer/spec-safe cleanup slices rather than a single mechanical pass. The rule stays off until that epic clears the debt, at which point it should be promoted to an error per #710's acceptance criteria.

## Formatting

- `npm run format:check` / `npm run format` (Prettier). Applies to the whole repo — remember to run it on new root-level `scripts/*.mjs` files too, not just `src/`.
Expand All @@ -67,6 +84,7 @@ Two related traps worth knowing:
- `.github/workflows/ci.yml` — installs both workspaces, runs `npm --prefix test-app test`, then `npm run coverage:check`.
- `.github/workflows/lint.yml` — format check, baseline-not-increased guard, `ng lint` + baseline gate for both workspaces.
- `.github/workflows/e2e.yml` — Playwright smoke test.
- `.github/workflows/security.yml` — DAST (OWASP ZAP) against `test-app` built in production mode; SAST is GitHub CodeQL default setup (no committed workflow). See `docs/security-scanning.md`.
- `.github/workflows/publish.yml` — npm Trusted Publisher (OIDC) flow, gated on `validate:publish`; only trigger is a GitHub Release (or a dry-run `workflow_dispatch`).

## Angular / TypeScript quirks
Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## Unreleased — Angular 19 → 20 stepped upgrade (consumer-impact)

**⚠️ Consumer coordination required.** Peer dependencies have been bumped to the Angular 20 line: `@angular/core`/`common`/`forms`/`router`/`animations`/`platform-browser`/`platform-browser-dynamic`/`platform-server` → `^20.3.30`, `@angular/cli` → `^20.3.36`, `@angular/cdk` → `^20.2.14`, `@fortawesome/angular-fontawesome` → `~3.0.0`, and the dev-only `typescript` constraint is now `5.8.3`. (`@angular/compiler` and `@angular/compiler-cli` are bumped in `test-app`'s own harness dependencies, not in this package's published `peerDependencies` — they aren't imported by `src/`.) As with the ngx-formly bump above, sam-ui-elements publishes raw TypeScript source, so these peer requirements are enforced by consumers' own `npm install` rather than a compiled build — **a consumer still on Angular 19 will fail dependency resolution against this version** until it completes its own Angular 20 upgrade. See GSA/sam-ui-elements#574 (part of the Angular 19→21 serial migration tracked in #562).

Also adds an explicit `@angular/core` peer (`^20.3.30`) that was previously missing despite hundreds of direct source imports.

The exported `Overlay` (`src/ui-kit/experimental/patterns/layout/components/core/overlay/index.ts`) constructor signature has changed: it no longer takes a `ComponentFactoryResolver` parameter (see below). Any consumer instantiating `Overlay` directly rather than via DI must update the call site.

Other notable internal fixes required to keep the library building/testing green under Angular/CDK 20:

- `@angular/cdk/portal`'s deprecated `PortalHost`/`DomPortalHost` aliases were removed; internal usages now use `PortalOutlet`/`DomPortalOutlet` directly (`src/ui-kit/experimental/patterns/layout/components/core/overlay/overlay-ref.ts`, `overlay.ts`). `Overlay`'s constructor no longer takes a `ComponentFactoryResolver` (removed/no longer needed by `DomPortalOutlet`).
- `@angular/cdk/table` no longer exports `CDK_TABLE_TEMPLATE`, `_CoalescedStyleScheduler`, or `_COALESCED_STYLE_SCHEDULER` (sticky-column scheduling moved to `afterNextRender` internally). `SamDataTableComponent` (`src/ui-kit/components/data-table/data-table.component.ts`) now inlines its own copy of the CDK table template instead of importing the removed constant, and no longer provides the removed style-scheduler token.
- Angular 20 no longer renders `ng-reflect-*` debug attributes in the DOM by default; a couple of specs (`sticky.spec.ts`, `name-entry.spec.ts`) that asserted on those attributes were rewritten to assert real component/DOM state instead.

## Unreleased — ngx-formly 6 → 7 migration (consumer-impact)

**⚠️ Consumer coordination required.** `src/formly` (`AbstractSamFormly` and its field/wrapper components) now targets `@ngx-formly/core@^7` — the peer dependency in `package.json` has been bumped from `^6.2.1`. Because sam-ui-elements publishes raw TypeScript source that consumers compile in-place, this peer requirement is enforced by `npm install` rather than by a compiled package build: **a consumer that keeps `@ngx-formly/core@6` installed will fail dependency resolution/installation against this version of sam-ui-elements** (an unmet peer dependency), even though the two versions are API-compatible for the subset of Formly this library uses.

Consumers (`iae-sam-front-end` and other repos importing `@gsa-sam/sam-ui-elements/src/formly/index`) **must bump their own `@ngx-formly/core` dependency to `^7.0.0` in lockstep** with adopting this release. No consumer code changes are required beyond the dependency bump itself. See GSA/sam-ui-elements#573.

## v0.9.19 (04/09/2019)

#### closed
Expand Down
Loading
Loading