seamshield 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +124 -2
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +1096 -0
  4. package/package.json +58 -4
  5. package/rules/secrets.patterns.yaml +28 -0
  6. package/rules/ss-agent-mcp-inline-credentials.yaml +23 -0
  7. package/rules/ss-agent-overbroad-permissions.yaml +27 -0
  8. package/rules/ss-agent-secrets-in-agent-files.yaml +22 -0
  9. package/rules/ss-auth-admin-route-unprotected.yaml +24 -0
  10. package/rules/ss-auth-api-route-no-auth.yaml +24 -0
  11. package/rules/ss-auth-client-only-guard.yaml +24 -0
  12. package/rules/ss-auth-cors-wildcard-with-credentials.yaml +21 -0
  13. package/rules/ss-client-firebase-admin-in-client.yaml +23 -0
  14. package/rules/ss-client-next-public-secret.yaml +33 -0
  15. package/rules/ss-client-server-secret-env-in-client.yaml +24 -0
  16. package/rules/ss-client-supabase-service-role-in-client.yaml +24 -0
  17. package/rules/ss-convex-internal-not-internal.yaml +25 -0
  18. package/rules/ss-convex-mutation-no-auth.yaml +26 -0
  19. package/rules/ss-deps-hallucinated-package.yaml +16 -0
  20. package/rules/ss-deps-known-vuln.yaml +16 -0
  21. package/rules/ss-deps-no-lockfile.yaml +17 -0
  22. package/rules/ss-deps-unpinned-spec.yaml +22 -0
  23. package/rules/ss-firebase-open-rules.yaml +22 -0
  24. package/rules/ss-secrets-env-file-committed.yaml +21 -0
  25. package/rules/ss-secrets-generic-credential-assignment.yaml +26 -0
  26. package/rules/ss-secrets-hardcoded-provider-key.yaml +47 -0
  27. package/rules/ss-secrets-private-key-file.yaml +22 -0
  28. package/rules/ss-secrets-supabase-service-role-key.yaml +25 -0
  29. package/rules/ss-supabase-permissive-policy.yaml +22 -0
  30. package/rules/ss-supabase-rls-disabled.yaml +22 -0
  31. package/schemas/finding.schema.json +92 -0
  32. package/index.js +0 -2
package/package.json CHANGED
@@ -1,8 +1,62 @@
1
1
  {
2
2
  "name": "seamshield",
3
- "version": "0.0.1",
4
- "description": "Security scanner for AI-built apps. Placeholder release v0.1 (scanner CLI + agent guard) is in active development.",
3
+ "version": "0.1.1",
4
+ "description": "Security scanner for AI-generated apps: finds the flaws vibecoded projects predictably ship",
5
+ "type": "module",
5
6
  "license": "MIT",
6
- "author": "wundder",
7
- "keywords": ["security", "scanner", "ai", "vibecoding", "secrets", "nextjs", "supabase"]
7
+ "homepage": "https://github.com/KaraboGerald/SeamShield#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/KaraboGerald/SeamShield.git",
11
+ "directory": "packages/cli"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/KaraboGerald/SeamShield/issues"
15
+ },
16
+ "keywords": [
17
+ "security",
18
+ "scanner",
19
+ "cli",
20
+ "ai",
21
+ "vibecoding",
22
+ "secrets",
23
+ "sast",
24
+ "nextjs",
25
+ "supabase",
26
+ "firebase",
27
+ "claude-code",
28
+ "cursor",
29
+ "osv",
30
+ "sarif"
31
+ ],
32
+ "bin": {
33
+ "seamshield": "dist/index.js"
34
+ },
35
+ "files": [
36
+ "README.md",
37
+ "dist",
38
+ "rules",
39
+ "schemas"
40
+ ],
41
+ "engines": {
42
+ "node": ">=20"
43
+ },
44
+ "scripts": {
45
+ "build": "tsup src/index.ts --format esm --clean && tsc -p tsconfig.build.json && rm -rf rules schemas && cp -R ../rules/rules ../rules/schemas . && cp ../../README.md README.md",
46
+ "prepack": "pnpm run build",
47
+ "test": "vitest run"
48
+ },
49
+ "dependencies": {
50
+ "commander": "^14.0.0",
51
+ "yaml": "^2.5.0",
52
+ "zod": "^4.0.0"
53
+ },
54
+ "devDependencies": {
55
+ "@seamshield/core": "workspace:*",
56
+ "@seamshield/rules": "workspace:*",
57
+ "@types/node": "^22.0.0",
58
+ "tsup": "^8.3.0",
59
+ "typescript": "^5.7.0",
60
+ "vitest": "^3.0.0"
61
+ }
8
62
  }
@@ -0,0 +1,28 @@
1
+ # Shared provider-key patterns consumed by ss/secrets/hardcoded-provider-key
2
+ # via `patterns_from`. Pattern names appear in finding titles, so keep them
3
+ # stable and provider-prefixed.
4
+ patterns:
5
+ - name: anthropic-api-key
6
+ regex: "sk-ant-[A-Za-z0-9_-]{20,}"
7
+ - name: openai-project-key
8
+ regex: "sk-proj-[A-Za-z0-9_-]{20,}"
9
+ - name: openai-legacy-key
10
+ regex: "sk-[A-Za-z0-9]{40,}"
11
+ - name: stripe-live-secret-key
12
+ regex: "sk_live_[A-Za-z0-9]{16,}"
13
+ - name: stripe-restricted-key
14
+ regex: "rk_live_[A-Za-z0-9]{16,}"
15
+ - name: stripe-webhook-secret
16
+ regex: "whsec_[A-Za-z0-9]{24,}"
17
+ - name: google-api-key
18
+ regex: "AIza[0-9A-Za-z_-]{35}"
19
+ - name: github-pat
20
+ regex: "ghp_[A-Za-z0-9]{36}"
21
+ - name: github-fine-grained-pat
22
+ regex: "github_pat_[A-Za-z0-9_]{36,}"
23
+ - name: aws-access-key-id
24
+ regex: "AKIA[0-9A-Z]{16}"
25
+ - name: slack-bot-token
26
+ regex: "xoxb-[0-9A-Za-z-]{20,}"
27
+ - name: convex-deploy-key
28
+ regex: "(?:prod|preview|dev):[a-z0-9-]+\\|[A-Za-z0-9+/=]{20,}"
@@ -0,0 +1,23 @@
1
+ id: ss/agent/mcp-inline-credentials
2
+ severity: high
3
+ title: Inline credential in MCP server configuration
4
+ description: >
5
+ An MCP configuration file passes a literal credential value instead of an
6
+ environment variable reference. MCP configs are commonly committed and
7
+ shared between machines, leaking the credential with them.
8
+ framework_ref: AI_AGENT_DROP_IN.md#default-security-stance
9
+ check:
10
+ type: regex
11
+ redact: true
12
+ include:
13
+ basenames: [".mcp.json", "mcp.json", "claude_desktop_config.json"]
14
+ patterns:
15
+ - name: inline-mcp-credential
16
+ regex: "\"[A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD)[A-Z0-9_]*\"\\s*:\\s*\"(?![$]|\\$\\{)[^\"]{12,}\""
17
+ fix:
18
+ summary: Reference the credential via ${ENV_VAR} and set it in the environment instead.
19
+ agent_prompt: >
20
+ Replace this literal value with an environment-variable reference (for
21
+ example "${GITHUB_TOKEN}") and document the variable in the project
22
+ README or .env.example. If this config file was committed with the real
23
+ value, tell the user to rotate the credential.
@@ -0,0 +1,27 @@
1
+ id: ss/agent/overbroad-permissions
2
+ severity: warn
3
+ title: Overbroad AI agent permission grant
4
+ description: >
5
+ An agent settings file grants blanket permissions — wildcard Bash,
6
+ allow-everything, or bypassing the permission system entirely. One
7
+ prompt-injected instruction then runs with full machine access.
8
+ framework_ref: AI_AGENT_DROP_IN.md#default-security-stance
9
+ check:
10
+ type: regex
11
+ include:
12
+ basenames: ["settings.json", "settings.local.json"]
13
+ path_contains: [".claude"]
14
+ patterns:
15
+ - name: wildcard-bash-allow
16
+ regex: "\"Bash\\(\\*"
17
+ - name: bypass-permissions-mode
18
+ regex: "bypassPermissions|dangerously-skip-permissions"
19
+ - name: allow-everything
20
+ regex: "\"allow\"\\s*:\\s*\\[\\s*\"\\*\""
21
+ fix:
22
+ summary: Replace blanket grants with specific allowed command patterns.
23
+ agent_prompt: >
24
+ Replace the wildcard grant with the specific commands the workflow
25
+ actually needs (for example "Bash(npm test:*)", "Bash(git status)").
26
+ Remove bypassPermissions/skip-permissions defaults so destructive
27
+ actions still prompt.
@@ -0,0 +1,22 @@
1
+ id: ss/agent/secrets-in-agent-files
2
+ severity: block
3
+ title: Provider API key in an AI agent instruction file
4
+ description: >
5
+ An agent instruction file (CLAUDE.md, AGENTS.md, .cursorrules, *.mdc)
6
+ contains a provider API key. These files are read into every agent
7
+ session, pasted into prompts, and frequently committed — a key here
8
+ leaks on every channel at once.
9
+ framework_ref: AI_AGENT_DROP_IN.md#default-security-stance
10
+ check:
11
+ type: regex
12
+ redact: true
13
+ include:
14
+ basenames: ["CLAUDE.md", "AGENTS.md", "GEMINI.md", ".cursorrules", ".clinerules", "*.mdc"]
15
+ patterns_from: secrets.patterns.yaml
16
+ fix:
17
+ summary: Remove the key from the instruction file and rotate it.
18
+ agent_prompt: >
19
+ Delete this key from the agent instruction file. If the agent needs the
20
+ credential, reference an environment variable name instead and load it
21
+ at runtime. Tell the user to rotate the key — instruction files are
22
+ copied into prompts and logs, so the value is burned.
@@ -0,0 +1,24 @@
1
+ id: ss/auth/admin-route-unprotected
2
+ severity: high
3
+ title: Admin route with no recognizable auth check
4
+ description: >
5
+ A page or route under an /admin/ path contains no recognizable
6
+ authentication or authorization marker. If a parent layout or middleware
7
+ guards it, suppress this with a seamshield-ignore comment; otherwise the
8
+ admin surface is open to anyone with the URL.
9
+ framework_ref: AI_AGENT_DROP_IN.md#default-security-stance
10
+ check:
11
+ type: absence
12
+ include:
13
+ path_contains: ["app/admin", "pages/admin"]
14
+ extensions: [".ts", ".tsx", ".js", ".jsx"]
15
+ patterns:
16
+ - name: auth-markers
17
+ regex: "auth|Auth|session|Session|currentUser|getUser|redirect|signIn|login|Login|clerk|Clerk|verif|jwt|JWT|cookie|Cookie|middleware"
18
+ fix:
19
+ summary: Add a server-side auth + role check before rendering admin content.
20
+ agent_prompt: >
21
+ Add a server-side authentication and role check at the top of this
22
+ admin page or its layout — for example, load the session, verify the
23
+ user has an admin role, and redirect to the login page otherwise. A
24
+ client-side check is not sufficient; the check must run on the server.
@@ -0,0 +1,24 @@
1
+ id: ss/auth/api-route-no-auth
2
+ severity: warn
3
+ title: API route with no recognizable auth or signature check
4
+ description: >
5
+ An API route handler contains no recognizable authentication,
6
+ authorization, or webhook-signature marker. Review whether this endpoint
7
+ should really be callable by anyone on the internet.
8
+ framework_ref: AI_AGENT_DROP_IN.md#default-security-stance
9
+ check:
10
+ type: absence
11
+ include:
12
+ path_contains: ["/api/"]
13
+ extensions: [".ts", ".js", ".tsx", ".jsx"]
14
+ patterns:
15
+ - name: auth-or-signature-markers
16
+ regex: "auth|Auth|session|Session|currentUser|getUser|clerk|Clerk|jwt|JWT|cookie|Cookie|bearer|Bearer|signature|webhook|svix|x-api-key|apiKey|API_KEY|verif|rate[Ll]imit|public"
17
+ fix:
18
+ summary: Authenticate the caller, or mark the route as intentionally public.
19
+ agent_prompt: >
20
+ Decide whether this API route should be public. If not, validate the
21
+ caller's session or API key at the top of the handler and return 401
22
+ before doing any work. If it is intentionally public (a health check,
23
+ a webhook with signature verification elsewhere), add a short comment
24
+ saying so and a seamshield-ignore for this rule.
@@ -0,0 +1,24 @@
1
+ id: ss/auth/client-only-guard
2
+ severity: warn
3
+ title: Auth guard that only runs in the browser
4
+ description: >
5
+ A "use client" component gates content behind a client-side user check.
6
+ Client-side guards are cosmetic — the data behind them must also be
7
+ protected on the server, or anyone can fetch it directly.
8
+ framework_ref: AI_AGENT_DROP_IN.md#default-security-stance
9
+ check:
10
+ type: regex
11
+ include:
12
+ extensions: [".ts", ".tsx", ".js", ".jsx"]
13
+ file_contains: "[\"']use client[\"']"
14
+ patterns:
15
+ - name: client-side-auth-gate
16
+ regex: "if\\s*\\(\\s*!\\s*(?:user|session|currentUser|isAuthenticated|isLoggedIn|loggedIn|signedIn|isSignedIn)\\b"
17
+ fix:
18
+ summary: Keep the UX guard, but enforce the same check server-side where the data is fetched.
19
+ agent_prompt: >
20
+ Keep this client-side check for UX, but verify that every piece of data
21
+ this component renders is also protected server-side — in the route
22
+ handler, server component, or database policy that produces it. If the
23
+ server already enforces it, suppress this finding with a
24
+ seamshield-ignore comment.
@@ -0,0 +1,21 @@
1
+ id: ss/auth/cors-wildcard-with-credentials
2
+ severity: high
3
+ title: CORS wildcard origin combined with credentials
4
+ description: >
5
+ This file allows any origin (Access-Control-Allow-Origin set from a
6
+ wildcard) while also enabling Access-Control-Allow-Credentials. That
7
+ combination lets any website act on behalf of your logged-in users.
8
+ framework_ref: AI_AGENT_DROP_IN.md#default-security-stance
9
+ check:
10
+ type: regex
11
+ file_contains: "Allow-Credentials[\"']?\\s*[,:=]\\s*[\"']?true"
12
+ patterns:
13
+ - name: cors-wildcard-origin
14
+ regex: "Allow-Origin[\"']?\\s*[,:=]\\s*[\"']\\*"
15
+ fix:
16
+ summary: Replace the wildcard with an explicit allowlist of trusted origins.
17
+ agent_prompt: >
18
+ Replace the wildcard origin with an explicit allowlist: read the
19
+ request Origin header, compare it against a fixed set of trusted
20
+ origins, and echo it back only on match. Keep Allow-Credentials only if
21
+ cookies or auth headers are genuinely needed cross-origin.
@@ -0,0 +1,23 @@
1
+ id: ss/client/firebase-admin-in-client
2
+ severity: block
3
+ title: firebase-admin imported in a client component
4
+ description: >
5
+ firebase-admin is the server-side SDK that bypasses all Firebase security
6
+ rules. Importing it in a "use client" file either breaks the build or, far
7
+ worse, bundles admin credentials into the browser bundle.
8
+ framework_ref: AI_AGENT_DROP_IN.md#framework-compromise-rule
9
+ check:
10
+ type: regex
11
+ include:
12
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs"]
13
+ file_contains: "[\"']use client[\"']"
14
+ patterns:
15
+ - name: firebase-admin-import
16
+ regex: "(?:from\\s+[\"']firebase-admin|require\\([\"']firebase-admin)"
17
+ fix:
18
+ summary: Move firebase-admin usage into server-only code; clients use the regular firebase SDK.
19
+ agent_prompt: >
20
+ Remove the firebase-admin import from this client component. Move the
21
+ admin operation into a server route handler or server action, expose
22
+ only the minimal result to the client, and use the regular `firebase`
23
+ client SDK (gated by security rules) for client-side reads.
@@ -0,0 +1,33 @@
1
+ id: ss/client/next-public-secret
2
+ severity: block
3
+ title: Secret exposed through a NEXT_PUBLIC_ variable
4
+ description: >
5
+ Next.js inlines every NEXT_PUBLIC_ variable into the client JavaScript
6
+ bundle at build time. A variable named NEXT_PUBLIC_*SECRET*, *SERVICE_ROLE*,
7
+ *PRIVATE*, *PASSWORD*, or *ADMIN* ships that value to every browser that
8
+ loads the app.
9
+ framework_ref: AI_AGENT_DROP_IN.md#framework-compromise-rule
10
+ check:
11
+ type: regex
12
+ include:
13
+ extensions:
14
+ - ".ts"
15
+ - ".tsx"
16
+ - ".js"
17
+ - ".jsx"
18
+ - ".mjs"
19
+ - ".cjs"
20
+ basenames:
21
+ - ".env*"
22
+ patterns:
23
+ - name: next-public-secret-name
24
+ regex: "NEXT_PUBLIC_[A-Z0-9_]*(?:SECRET|SERVICE_ROLE|PRIVATE|PASSWORD|ADMIN)[A-Z0-9_]*"
25
+ fix:
26
+ summary: Rename the variable to drop the NEXT_PUBLIC_ prefix and read it only on the server.
27
+ agent_prompt: >
28
+ Remove the NEXT_PUBLIC_ prefix from this variable so Next.js stops
29
+ inlining it into the client bundle. Move every read of it into
30
+ server-only code (a route handler, server action, or server component)
31
+ and pass only derived, non-secret data to client components. If the app
32
+ was already deployed with this variable, tell the user to rotate the
33
+ underlying credential.
@@ -0,0 +1,24 @@
1
+ id: ss/client/server-secret-env-in-client
2
+ severity: high
3
+ title: Server secret environment variable read in a client component
4
+ description: >
5
+ A "use client" file reads a secret-named environment variable. In the
6
+ browser bundle that read resolves to undefined at best — and if the
7
+ bundler inlines it, the secret ships to every visitor.
8
+ framework_ref: AI_AGENT_DROP_IN.md#framework-compromise-rule
9
+ check:
10
+ type: regex
11
+ include:
12
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs"]
13
+ file_contains: "[\"']use client[\"']"
14
+ patterns:
15
+ - name: server-secret-env-read
16
+ regex: "process\\.env\\.(?!NEXT_PUBLIC_)[A-Z0-9_]*(?:SECRET|PRIVATE|PASSWORD|SERVICE_ROLE|API_KEY|TOKEN)[A-Z0-9_]*"
17
+ fix:
18
+ summary: Move the env read into server code and pass only derived, non-secret data to the client.
19
+ agent_prompt: >
20
+ Move this environment variable read out of the client component into a
21
+ server component, route handler, or server action. Pass only the
22
+ derived, non-secret result down as props. Never rename the variable to
23
+ NEXT_PUBLIC_ to "fix" the undefined value — that ships the secret to
24
+ the browser.
@@ -0,0 +1,24 @@
1
+ id: ss/client/supabase-service-role-in-client
2
+ severity: block
3
+ title: Supabase service-role key referenced in a client component
4
+ description: >
5
+ A "use client" file references the Supabase service-role key. The
6
+ service-role key bypasses Row Level Security; any reference in
7
+ client-bundled code risks shipping full database access to every browser.
8
+ framework_ref: AI_AGENT_DROP_IN.md#framework-compromise-rule
9
+ check:
10
+ type: regex
11
+ include:
12
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs"]
13
+ file_contains: "[\"']use client[\"']"
14
+ patterns:
15
+ - name: service-role-reference
16
+ regex: "SUPABASE_SERVICE_ROLE"
17
+ fix:
18
+ summary: Use the anon key plus RLS in client code; keep the service-role key server-side.
19
+ agent_prompt: >
20
+ Replace this service-role usage with the public anon key and rely on
21
+ Row Level Security policies for authorization. Any operation that truly
22
+ needs service-role privileges must move to a server route handler or
23
+ edge function. If this code ever shipped, tell the user to rotate the
24
+ service-role key.
@@ -0,0 +1,25 @@
1
+ id: ss/convex/internal-not-internal
2
+ severity: warn
3
+ title: Function named internal* but exported as public
4
+ description: >
5
+ A Convex function whose name starts with "internal" is declared with the
6
+ public mutation/query/action constructor. The name says private, the
7
+ declaration says public — anyone can call it.
8
+ framework_ref: AI_AGENT_DROP_IN.md#default-security-stance
9
+ check:
10
+ type: regex
11
+ include:
12
+ path_contains: ["convex/"]
13
+ extensions: [".ts", ".js"]
14
+ exclude:
15
+ dirs: ["_generated"]
16
+ patterns:
17
+ - name: internal-named-public-fn
18
+ regex: "const\\s+internal[A-Z]\\w*\\s*=\\s*(?:mutation|query|action)\\s*\\("
19
+ fix:
20
+ summary: Declare it with internalMutation/internalQuery/internalAction instead.
21
+ agent_prompt: >
22
+ Change this declaration to the internal variant (internalMutation,
23
+ internalQuery, or internalAction from ./_generated/server) so it can
24
+ only be invoked by other Convex functions, and update any client-side
25
+ callers — they should not exist if the name is accurate.
@@ -0,0 +1,26 @@
1
+ id: ss/convex/mutation-no-auth
2
+ severity: warn
3
+ title: Public Convex mutation with no auth check
4
+ description: >
5
+ A public Convex mutation never consults ctx.auth. Public mutations are
6
+ callable by anyone who can reach the deployment URL — review whether this
7
+ one should verify the caller first.
8
+ framework_ref: AI_AGENT_DROP_IN.md#default-security-stance
9
+ check:
10
+ type: absence
11
+ include:
12
+ path_contains: ["convex/"]
13
+ extensions: [".ts", ".js"]
14
+ exclude:
15
+ dirs: ["_generated"]
16
+ file_contains: "(?<![A-Za-z])mutation\\s*\\("
17
+ patterns:
18
+ - name: convex-auth-markers
19
+ regex: "ctx\\.auth|getAuthUserId|getUserIdentity|requireAuth|internalMutation"
20
+ fix:
21
+ summary: Verify the caller with ctx.auth at the top of the mutation, or make it internal.
22
+ agent_prompt: >
23
+ Add an auth check at the top of this mutation — for example `const
24
+ identity = await ctx.auth.getUserIdentity(); if (!identity) throw new
25
+ Error("Unauthorized");` — or convert it to internalMutation if it
26
+ should only ever be called from other server functions.
@@ -0,0 +1,16 @@
1
+ id: ss/deps/hallucinated-package
2
+ severity: block
3
+ title: Dependency does not exist on npm
4
+ description: >
5
+ AI agents sometimes invent package names. Installing a newly created or
6
+ squatted package with that name later can execute attacker-controlled code.
7
+ framework_ref: AI_AGENT_DROP_IN.md#supply-chain-stance
8
+ check:
9
+ type: builtin
10
+ builtin: hallucinated-package
11
+ fix:
12
+ summary: Replace the dependency with a real maintained package or remove it.
13
+ agent_prompt: >
14
+ Verify the intended package name against npm and the project docs. Replace
15
+ this dependency with the maintained package that provides the needed API,
16
+ update imports, reinstall, and commit the updated lockfile.
@@ -0,0 +1,16 @@
1
+ id: ss/deps/known-vuln
2
+ severity: high
3
+ title: Dependency version has known vulnerabilities
4
+ description: >
5
+ The pinned dependency version is reported by OSV for the npm ecosystem.
6
+ Known exploited vulnerability indicators are escalated to block severity.
7
+ framework_ref: seamshield-final-framework/adoption-contract/.seamshield/release-gates.yaml
8
+ check:
9
+ type: builtin
10
+ builtin: known-vuln
11
+ fix:
12
+ summary: Upgrade the dependency to a non-vulnerable version and refresh the lockfile.
13
+ agent_prompt: >
14
+ Upgrade this dependency to the nearest non-vulnerable version that satisfies
15
+ the project, update the lockfile, run the affected tests, and document any
16
+ breaking API changes.
@@ -0,0 +1,17 @@
1
+ id: ss/deps/no-lockfile
2
+ severity: warn
3
+ title: package.json with no lockfile
4
+ description: >
5
+ No lockfile sits next to package.json, so every install re-resolves
6
+ dependency versions. Builds are not reproducible, and a hijacked patch
7
+ release of any dependency lands silently on the next install.
8
+ framework_ref: AI_AGENT_DROP_IN.md#supply-chain-stance
9
+ check:
10
+ type: builtin
11
+ builtin: no-lockfile
12
+ fix:
13
+ summary: Commit a lockfile (pnpm-lock.yaml, package-lock.json, or yarn.lock).
14
+ agent_prompt: >
15
+ Run the project's package manager install once (npm install / pnpm
16
+ install / yarn) and commit the generated lockfile. Make sure .gitignore
17
+ does not exclude it.
@@ -0,0 +1,22 @@
1
+ id: ss/deps/unpinned-spec
2
+ severity: info
3
+ title: Dependency pinned to "latest" or "*"
4
+ description: >
5
+ A dependency version of "latest" or "*" resolves to whatever the
6
+ registry serves at install time, making builds unreproducible and
7
+ auto-adopting compromised releases.
8
+ framework_ref: AI_AGENT_DROP_IN.md#supply-chain-stance
9
+ check:
10
+ type: regex
11
+ include:
12
+ basenames: ["package.json"]
13
+ patterns:
14
+ - name: latest-or-star-spec
15
+ regex: "\"\\s*:\\s*\"(?:\\*|latest)\""
16
+ fix:
17
+ summary: Pin the dependency to a specific version or range.
18
+ agent_prompt: >
19
+ Replace the "latest" or "*" specifier with the currently installed
20
+ version (check the lockfile or node_modules/<pkg>/package.json) using a
21
+ caret range, for example "^2.4.1", then reinstall to update the
22
+ lockfile.
@@ -0,0 +1,22 @@
1
+ id: ss/firebase/open-rules
2
+ severity: block
3
+ title: Firebase security rules allow unrestricted access
4
+ description: >
5
+ A Firebase rules file grants read or write with "if true". Every
6
+ document or object it covers is readable or writable by anyone on the
7
+ internet, no authentication required.
8
+ framework_ref: AI_AGENT_DROP_IN.md#default-security-stance
9
+ check:
10
+ type: regex
11
+ include:
12
+ basenames: ["*.rules"]
13
+ patterns:
14
+ - name: allow-if-true
15
+ regex: "allow\\s+[a-z, ]+:\\s*if\\s+true"
16
+ fix:
17
+ summary: Require auth and ownership in the rule condition.
18
+ agent_prompt: >
19
+ Replace `if true` with a real condition — at minimum `if request.auth
20
+ != null`, and for user data `if request.auth.uid == resource.data.
21
+ ownerId` (or the equivalent path check). Deploy the updated rules and
22
+ verify access with the Firebase rules simulator.
@@ -0,0 +1,21 @@
1
+ id: ss/secrets/env-file-committed
2
+ severity: block
3
+ title: .env file is committed or not git-ignored
4
+ description: >
5
+ Dotenv files hold live credentials. If a .env file is tracked by git, or
6
+ sits untracked without a .gitignore entry, the next `git add .` ships every
7
+ secret in it to the remote — the single most common leak in AI-generated
8
+ repos.
9
+ framework_ref: AI_AGENT_DROP_IN.md#default-security-stance
10
+ check:
11
+ type: builtin
12
+ builtin: env-file-committed
13
+ fix:
14
+ summary: Git-ignore the .env file, untrack it, and rotate every credential it contains.
15
+ agent_prompt: >
16
+ Add `.env` and `.env.*` (with an exception for `.env.example`) to
17
+ .gitignore. For each dotenv file already tracked by git, run
18
+ `git rm --cached <file>` so it stays on disk but leaves the index. Create
19
+ a `.env.example` listing variable names with empty values. Finally, tell
20
+ the user every credential in the file must be rotated at its provider,
21
+ because git history still contains the old values.
@@ -0,0 +1,26 @@
1
+ id: ss/secrets/generic-credential-assignment
2
+ severity: high
3
+ title: Credential-looking literal assigned in source
4
+ description: >
5
+ A variable or property whose name says secret, token, password, or apikey
6
+ is assigned a long literal value in source code. Even when the value is
7
+ not a recognized provider key, committing credentials to source is unsafe.
8
+ framework_ref: AI_AGENT_DROP_IN.md#default-security-stance
9
+ check:
10
+ type: regex
11
+ redact: true
12
+ include:
13
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".rb", ".go", ".yaml", ".yml", ".toml"]
14
+ exclude:
15
+ basenames: [".env*", "package-lock.json", "pnpm-lock.yaml", "yarn.lock", "*.min.js", "*.test.ts", "*.test.js", "*.spec.ts", "*.spec.js"]
16
+ dirs: ["test", "tests", "__tests__", "fixtures", "__fixtures__", "__mocks__"]
17
+ patterns:
18
+ - name: generic-credential
19
+ regex: "(?:[Ss][Ee][Cc][Rr][Ee][Tt]|[Tt][Oo][Kk][Ee][Nn]|[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd]|[Aa][Pp][Ii][_-]?[Kk][Ee][Yy])[A-Za-z0-9_]*[\"']?\\s*[:=]\\s*[\"'][A-Za-z0-9+/_-]{32,}[\"']"
20
+ fix:
21
+ summary: Move the value into an environment variable and rotate it if it was real.
22
+ agent_prompt: >
23
+ Replace this literal with a read from a server-side environment
24
+ variable, add the variable name to .env.example with a placeholder
25
+ value, and ask the user whether the committed value was a real
26
+ credential — if so they must rotate it.
@@ -0,0 +1,47 @@
1
+ id: ss/secrets/hardcoded-provider-key
2
+ severity: block
3
+ title: Hardcoded provider API key in source
4
+ description: >
5
+ A live API key (Anthropic, OpenAI, Stripe, Google, GitHub, AWS, Slack,
6
+ Convex) is written directly in source code. Anyone with repo read access —
7
+ or anyone who fetches the client bundle — can spend on your account.
8
+ framework_ref: AI_AGENT_DROP_IN.md#default-security-stance
9
+ check:
10
+ type: regex
11
+ patterns_from: secrets.patterns.yaml
12
+ redact: true
13
+ include:
14
+ extensions:
15
+ - ".ts"
16
+ - ".tsx"
17
+ - ".js"
18
+ - ".jsx"
19
+ - ".mjs"
20
+ - ".cjs"
21
+ - ".json"
22
+ - ".yaml"
23
+ - ".yml"
24
+ - ".toml"
25
+ - ".py"
26
+ - ".rb"
27
+ - ".go"
28
+ - ".html"
29
+ - ".svelte"
30
+ - ".vue"
31
+ - ".astro"
32
+ exclude:
33
+ basenames:
34
+ - ".env*"
35
+ - "package-lock.json"
36
+ - "pnpm-lock.yaml"
37
+ - "yarn.lock"
38
+ - "bun.lockb"
39
+ - "*.min.js"
40
+ fix:
41
+ summary: Move the key into an environment variable and rotate it at the provider.
42
+ agent_prompt: >
43
+ Replace the hardcoded key with a server-side environment variable read
44
+ (for example `process.env.PROVIDER_API_KEY`), add the variable name to
45
+ `.env.example`, and make sure the file that reads it only runs on the
46
+ server. Then tell the user to rotate this key at the provider dashboard —
47
+ treat the committed value as burned.
@@ -0,0 +1,22 @@
1
+ id: ss/secrets/private-key-file
2
+ severity: block
3
+ title: Private key material committed to the repo
4
+ description: >
5
+ A PEM-encoded private key (RSA, EC, OpenSSH, or PKCS#8) is present in the
6
+ repository. Private keys in git history stay compromised forever, even
7
+ after deletion.
8
+ framework_ref: AI_AGENT_DROP_IN.md#default-security-stance
9
+ check:
10
+ type: regex
11
+ exclude:
12
+ basenames: ["*.pub"]
13
+ patterns:
14
+ - name: pem-private-key
15
+ regex: "-----BEGIN (?:RSA |EC |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY-----"
16
+ fix:
17
+ summary: Remove the key file, gitignore it, and rotate the key pair.
18
+ agent_prompt: >
19
+ Remove this private key file from the repository, add its path (or
20
+ *.pem / *.key) to .gitignore, and tell the user to rotate the key pair
21
+ everywhere it is used — git history preserves the old key, so deletion
22
+ alone does not make it safe.