Skip to content

ci(sast): add FerrLabs OpenGrep ruleset from the org audit - #190

Merged
BryanFRD merged 4 commits into
mainfrom
ci/opengrep-ferrlabs-rules
Jul 31, 2026
Merged

ci(sast): add FerrLabs OpenGrep ruleset from the org audit#190
BryanFRD merged 4 commits into
mainfrom
ci/opengrep-ferrlabs-rules

Conversation

@BryanFRD

@BryanFRD BryanFRD commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Closes #189

Adds a FerrLabs OpenGrep ruleset and wires it into the shared security scan, so the patterns the org audit found are checked on every push instead of once.

Two parts

1. sast/ferrlabs.yml — six rules, each encoding a defect that recurred in more than one repo:

Rule From
native-browser-dialog CLAUDE.md hard rule — org is currently clean, this keeps it that way
stringly-typed-domain FerrTrack-Cloud#519, Status#137
jwt-validation-without-issuer FerrLabs-Cloud#721
unbounded-public-query FerrTrack-Cloud#520
hardcoded-plan-limit FerrTrack-Cloud#522
local-ui-primitive UI#333 + its four consumer issues

2. reusable-security-scan.yml applies it by default. 19 repos already call this workflow, so they all pick the rules up with no change on their side — the ruleset is passed as a second -f alongside whatever sast-config resolves to, rather than replacing it.

Two new inputs:

  • ferrlabs-rules (default true) — opt out per repo
  • ferrlabs-rules-ref (default main) — pin to a SHA to freeze the rules for a repo

The ruleset is fetched into $RUNNER_TEMP, not the workspace, so it is not itself part of the scanned tree. If the fetch fails the step warns and the scan continues on the community set — our own rules being unreachable must never break a security scan.

Verified by running the engine

semgrep (OpenGrep is a fork of it, same rule format) was installed locally and every rule was executed against purpose-built fixtures.

Config parses: found 0 configuration error(s), and 6 rule(s).

Positive cases — each fires at the right file and line:

ferrlabs-native-browser-dialog          bad-dialog.ts:2   (window.confirm)
ferrlabs-native-browser-dialog          bad-dialog.ts:3   (window.alert)
ferrlabs-jwt-validation-without-issuer  jwt.rs:2          (Validation::default())
ferrlabs-unbounded-public-query         public.rs:2       (fetch_all, no LIMIT)

Negative controls — no findings, as intended:

good.ts          this.modal.confirm({...})   → not flagged
login_fixed.rs   dummy_hash() constant-time  → not flagged

The three regex rules were separately run against the real files they were derived from: stringly-typed found 4 in FerrTrack/models.rs, hardcoded-plan-limit found 1 in FerrTrack/rules.rs, local-ui-primitive found 5 in FerrVault/editorial.ts.

One rule was written, tested, and removed

login-timing-oracle (for FerrGrowth-Cloud#599) is not in this PR. It matched the vulnerable login and a correctly-fixed one using dummy_hash — the let ... else + ... sequence pattern is too loose to distinguish them. A rule that flags correct code trains people to ignore the scanner, so it is better absent until it can be written precisely. #599 still tracks the underlying issue.

Rollout note

fail-on-sast stays false by default, so this is advisory on merge — findings land in the Security tab, nothing blocks. Measure the noise before gating.

Expect local-ui-primitive to need tuning: FerrGames' fg-game-card is a legitimate product component that matches the pattern and is not a duplication of anything shared.

@ferrfleet ferrfleet Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new sast/ferrlabs.yml ruleset. The three regex-based rules match the counts verified in the PR description, and the other three AST rules (native-browser-dialog, login-timing-oracle, jwt-validation-without-issuer) look structurally sound as pattern syntax. One correctness issue found in the fourth unverified AST rule — see inline comment (Blocking). Nothing else blocking; the known fg-game-card false positive on local-ui-primitive is already called out and fine to tune later since this is advisory-only for now.

Comment thread sast/ferrlabs.yml Outdated
Comment on lines +83 to +86
- pattern: $Q.fetch_all(...)
- pattern-not-inside: |
$S = "...LIMIT...";
...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: this exclusion can't do what the message promises. $S is a fresh metavariable never unified with $Q, so pattern-not-inside only checks whether some string assignment containing the literal text ...LIMIT... exists in the enclosing scope — not whether the string actually passed to $Q.fetch_all(...) contains LIMIT. Separately, ... inside a quoted-string pattern is matched as literal dot characters by the Rust matcher, not as an ellipsis wildcard, so in practice this clause will essentially never match anything real. Net effect: the rule fires on every fetch_all(...) under routes/public*.rs regardless of whether a LIMIT is present — the opposite of advisory-quality (guaranteed noise, not a missed case).

Since fetch_all's query is usually built through a query-builder rather than a raw string literal, string-content matching probably isn't the right tool here anyway. Worth reworking (e.g. metavariable-pattern tied to $Q, or dropping the exclusion for a manual follow-up pass) before relying on this one for signal — this is one of the four AST rules the PR description flags as unverified, so flagging it now per that ask.

Comment thread .github/workflows/reusable-security-scan.yml Fixed

@ferrfleet ferrfleet Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up on the new commit (0a94eff), which wires sast/ferrlabs.yml into reusable-security-scan.yml by default. One nit inline on the newly added code, matching an existing code-scanning alert. The rest of the wiring looks correct: REF is properly passed via env:, a failed curl degrades to a warning instead of failing the step, and the existing OpenGrep exit-code handling is untouched.

The Blocking finding from my previous review (the ferrlabs-unbounded-public-query rule's broken pattern-not-inside exclusion, sast/ferrlabs.yml#L86) is unchanged in this commit — still open, not re-stating details.

Comment thread .github/workflows/reusable-security-scan.yml Outdated
BryanFRD and others added 2 commits July 31, 2026 17:35
@BryanFRD
BryanFRD merged commit b1f6c06 into main Jul 31, 2026
9 checks passed
@BryanFRD
BryanFRD deleted the ci/opengrep-ferrlabs-rules branch July 31, 2026 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci(sast): add a FerrLabs OpenGrep ruleset encoding the org audit findings

2 participants