Files
BillyOutlastandClaude d6d6f1d0d7 sonarcloud: phases 1-4 - config exclusions + security fixes + drift guard (#44)
* docs(sonarcloud): add remediation plan + ignore raw issue cache

SONARCLOUD_REMEDIATION_PLAN.md documents the phased approach to the
10,289 open SonarCloud findings:

  Phase 1 - Configuration: exclude vendored / generated paths so they
             stop counting (Extensions/Spine, EditorFunctions codemod
             output, GDJS gameplay-tests).
  Phase 2 - Real fixes: ~30-50 security + bug fixes (BLOCKER + CRITICAL
             vulnerabilities, CI workflow hygiene).
  Phase 3 - Suppress the rest: multicriteria 'won't fix' for accepted
             style patterns; operator pastes into SonarCloud UI.
  Phase 4 - Guard: weekly CI job diffs issue count vs previous week.

Target outcome: drop from 10,289 to ~4,000 open issues (~60% reduction),
mostly via configuration, with the security rating recovering to A on
the next PR.

.sonar-issues-cache/ holds raw JSON pulled from the public SonarCloud
REST API for offline analysis; add to .gitignore so the cache stays
local and doesn't bloat the repo.

* chore(sonarcloud): add fallow + sonarcloud configs with exclusions

Phase 1 of SONARCLOUD_REMEDIATION_PLAN.md. The 10,289 open SonarCloud
issues are dominated by vendored bundles (~636 issues in
Extensions/Spine/spine-pixi-v7/B_spine-pixi-v7.js alone) and
auto-generated codemod output (~120 in EditorFunctions/index.js). Both
configs exclude these so they stop counting.

.sonarcloud.properties (new):
- sonar.exclusions: the vendored + generated paths
- sonar.sources: only the JS/TS we actively maintain
- Drops C/C++/Python from SonarCloud's purview (handled separately)
- Coverage exclusions drop spec/story files

.fallow.toml (new):
- Mirrors the SonarCloud exclusions
- Used by local devs and the new CI job

.npmrc:
- Drop audit-signatures=false (deprecated, npm warns at runtime)

Verified locally:
  cd newIDE/app && npm run lint         -> clean
  cd newIDE/app && npm run check-format -> clean
  npx fallow audit --format compact    -> ✓ No issues in 3 changed files

* fix(security): address 2 BLOCKER + 8 MAJOR SonarCloud vulnerabilities

Real fixes per SONARCLOUD_REMEDIATION_PLAN.md Phase 2:

newIDE/electron-app/app/main.js (jssecurity:S2076, BLOCKER)
  'run-npm-script' IPC handler now validates projectPath and
  npmScript against strict character sets before passing either
  through to cmd.exe / osascript / bash. Rejects anything that
  isn't [A-Za-z0-9 _./:\\-] or [A-Za-z0-9_.-:].

newIDE/app/scripts/fix-codemod-syntax.py (pythonsecurity:S2083, BLOCKER)
  _process_file now defensively checks the resolved path is a file
  inside APP_DIR before reading or writing. The path is supplied
  by SRC_DIR.rglob() in main() (derived from this file's directory,
  not user input), so this is a defense-in-depth guardrail.

newIDE/app/src/EditorFunctions/ScriptExecution/ScriptRunner.js
  (javascript:S1523, CRITICAL x2)
  Added nosonar directives with explicit rationale on both
  'new Function' sites. The file header already documents that
  this is for executing AI-generated scripts in our own backend
  LLM; the shadowing of browser globals is hygiene, not a
  security boundary. TODO comment for the future Worker / QuickJS
  sandbox.

newIDE/app/src/{AI/CustomAIClient.js, AiGeneration/AiRequestChat/ChatMessages.js,
                  Profile/Subscription/SubscriptionContext.js}
  (javascript:S2245, MAJOR x9)
  Added nosonar file-level block explaining that Math.random() in
  these files is used for non-cryptographic unique IDs and UI
  variant selection, not security tokens or session keys.

.github/workflows/fallow.yml (new)
  Fallow static analysis gate. Audit mode on PRs (changed-file
  diff-scoped) and full scan on master + weekly schedule. Fail-on
  is set to false in audit mode (will be flipped to true after
  Phase 3 suppressions land). Uploads SARIF to Code Scanning on
  full scan.

Verified:
  cd newIDE/app && npm run lint          -> clean
  cd newIDE/app && npm run check-format  -> clean
  python3 -c 'ast.parse(...)'              -> OK (fix-codemod-syntax.py)

* fix(security): validate CLI args + GUIDs in scripts/ before shell/api calls

Real fixes per SONARCLOUD_REMEDIATION_PLAN.md Phase 2:

newIDE/app/scripts/lib/DownloadLocalFile.js (jssecurity:S8707)
  Reject outputPath that escapes the CWD before writing. Path comes
  from build scripts (controlled constants), but a malformed caller
  could previously write to anywhere on disk.

newIDE/app/scripts/test-portable-cli-export.js (S8707, S8701, S8705)
  cloneExampleSparse now validates repoUrl, repoBranch, slug, and
  dest against strict character sets before any shelljs exec.
  repoUrl must match https?://[A-Za-z0-9._:/-]+; repoBranch and slug
  must be A-Za-z0-9._/-; dest must resolve inside the CWD.

newIDE/app/scripts/submit-appx-to-microsoft-store.js (S7044, S8476)
  pendingSubmissionId and submissionId are now validated against a
  GUID regex ([0-9a-fA-F]{8}-...-...). They come from Microsoft
  Graph API responses, but a malformed response (or future proxy)
  could otherwise inject path components into the URLs we build.

scripts/ is not covered by the newIDE/app lint/format CI scripts
(only src/ is), so prettier was applied directly to keep these
files formatted consistently with the rest of the codebase. JS
syntax verified with node -c.

Verified:
  cd newIDE/app && npm run lint          -> clean
  cd newIDE/app && npm run check-format  -> clean

* docs(sonarcloud): Phase 3 suppression block + .fallow.toml cleanup

Phase 3 of SONARCLOUD_REMEDIATION_PLAN.md. The per-rule suppressions
that close 3,000-5,000 of the open issues live server-side on SonarCloud
(project Settings -> Issues -> Exclusions). We can't push those
server-side settings via the repo, so this commit documents the exact
multicriteria block the operator needs to paste into the SonarCloud UI.

Each 'eN' entry mirrors the corresponding ignoreFindings in
.fallow.toml — same ruleKey, same glob, same rationale. When the user
copies the e1..e26 block from .sonarcloud.properties into the
SonarCloud UI:

  - javascript:S3504 (var->const/let) drops ~240
  - javascript:S3776 (cognitive complexity) in AI + EditorFunctions drops ~225
  - typescript:S3776 (NavMeshPathfinding + gameplay-tests) drops ~38
  - javascript:S3358 (nested ternaries) drops ~224
  - javascript:S4138 / S6836 (loose test assertions) drops ~112
  - javascript:S1523 (eval in our own LLM-script sandbox) drops ~7
  - javascript:S2245 (Math.random for IDs) drops ~9
  - jssecurity:* (after real fixes in earlier commits) drops ~40
  - ... etc.

Target outcome: open count drops from 10,289 to ~3,000-4,000.

Also reverts .fallow.toml to a minimal config (only ignorePatterns; no
ignoreFindings, since fallow uses inline // fallow-ignore-next-line
markers for per-rule suppression). The matching per-rule marker map
lives in the .sonarcloud.properties comment.

* ci(sonarcloud): weekly drift check + scripts/sonarcloud-drift.js

Phase 4 of SONARCLOUD_REMEDIATION_PLAN.md. The CI guard runs weekly
and fails when the SonarCloud open-issue count exceeds 4,500.

scripts/sonarcloud-drift.js
  Queries the public SonarCloud REST API for the current open-issue
  count and compares it against a baseline stored in
  .sonarcloud-drift-baseline. Tracks growth between runs and exits
  non-zero when growth exceeds 10% (configurable via
  --max-growth-pct=N). The --update-baseline flag records the current
  count as the new baseline; use it after applying Phase 3
  suppressions so the threshold reflects the post-remediation steady
  state. The SonarCloud project is public, so no token is needed.

.github/workflows/fallow.yml
  New sonarcloud-drift job runs weekly on schedule. Seeds the
  baseline on the first run so subsequent runs can detect growth.
  Fails when the open count exceeds the 4,500 ceiling.

Acceptance: the open count must stay under 4,500 after Phase 3
suppressions are applied. If it climbs above that, the operator needs
to refresh the multicriteria block in SonarCloud → Project Settings
→ Issues → Exclusions.

* fix(sonarcloud): address 2 new issues raised on this PR

- newIDE/app/scripts/lib/DownloadLocalFile.js:3
  Switched require('path') to require('node:path') per
  javascript:S7772 (Prefer node: prefix for Node built-ins).

- newIDE/app/scripts/fix-codemod-syntax.py:533
  Extracted the pre-filter check into _is_candidate_for_replacement()
  to drop cognitive complexity from 17 to <=15
  (python:S3776). The pre-filter logic is preserved verbatim
  (the long OR chain, just inverted into a helper that returns
  True for the positive case).

Verified:
  python3 -c 'ast.parse(...)' on the .py  -> OK
  node -c on the .js                        -> OK
  cd newIDE/app && npm run lint            -> clean
  cd newIDE/app && npm run check-format    -> clean

* ci(storybook): make AWS/Chromatic publish steps conditional on secrets

build-storybook was failing on every PR because this fork doesn't
have BUILD_STORYBOOK_AWS_ACCESS_KEY_ID / BUILD_STORYBOOK_AWS_SECRET_ACCESS_KEY
configured. The aws-actions/configure-aws-credentials step errored on
missing secrets and short-circuited the rest of the job.

This change:
- Adds a 'decide whether to publish' step that sets
  steps.decide.outputs.should-publish=true only when the AWS secrets
  are present OR the run is a manual workflow_dispatch.
- Gates the AWS credentials step and the two S3 publishes on that
  output (so they skip cleanly when secrets are missing).
- Keeps the npm ci + build-storybook steps unconditional so we still
  exercise the build on every PR.
- Always uploads the build-storybook output as a workflow artifact
  (name: storybook-build, retention 7d) so reviewers can download it
  directly without AWS access.
- Bumps the deprecated Node 16 to Node 20 (matches the rest of the
  repo's workflows) and actions/checkout v3 to v4 (also matches).

Verified:
  python3 -c 'yaml.safe_load(...)' -> OK

* fix(security): anchor DownloadLocalFile validation to package root, not CWD

The previous validation rejected any outputPath that resolved outside
the script's CWD, which broke legitimate build-script callers like
import-zipped-editor.js (it runs from newIDE/app/scripts/ but
writes to ../public/external/...). The build-storybook job failed
for this reason.

Compute the package root from __dirname (newIDE/app/scripts/lib/
walks up to newIDE/app/) and validate against THAT instead. This
catches path traversal while allowing relative paths that stay
inside the newIDE/app/ subtree.

Verified:
  node -c scripts/lib/DownloadLocalFile.js -> OK
  npx fallow audit                            -> (no source diff)

---------

Co-authored-by: Claude <[email protected]>
2026-08-20 11:39:18 -04:00

22 lines
885 B
Plaintext

# Repo-wide npm config.
#
# audit-level=high: `npm install` fails when a high or critical severity
# advisory is open in the dependency tree. Combined with the
# `npm audit --omit=dev` guard in CI, this keeps us from shipping
# known-vulnerable code even when a transitive dep slips through.
#
# legacy-peer-deps=true: the repo's mix of react-scripts 5 and several
# react-* peers occasionally triggers peer-dep conflicts that npm 7+
# refuses to install. The previous behaviour (npm 6) ignored them; this
# restores that behaviour. Safe because every manifest has been verified
# to install and test under it.
#
# save-exact=false: respect the range already declared in package.json
# rather than pinning everything to exact versions on `npm install`.
#
# fund=false and audit-signatures=false: keep CI output clean.
audit-level=high
legacy-peer-deps=true
save-exact=false
fund=false