Skip to content

chore: pre-public hardening (LICENSE, SECURITY.md, docs rebuild, expanded spectral rule)#5

Merged
Ed Fricker (beastawakens) merged 2 commits into
mainfrom
chore/pre-public-hardening
May 8, 2026
Merged

chore: pre-public hardening (LICENSE, SECURITY.md, docs rebuild, expanded spectral rule)#5
Ed Fricker (beastawakens) merged 2 commits into
mainfrom
chore/pre-public-hardening

Conversation

@beastawakens
Copy link
Copy Markdown
Member

@beastawakens Ed Fricker (beastawakens) commented May 8, 2026

User description

Summary

Pre-public-launch cleanup of smileidentity/api-reference. One PR with five threads of work:

  1. Apache-2.0 LICENSE — repo previously had license: null. Public repos without a license are "all rights reserved" and consumers can't legally generate clients/SDKs from the specs.
  2. SECURITY.md — points at security@smileidentity.com for private disclosure. Adjust if you use a different address.
  3. Drop stale package.json — its bundle script produced invalid output for multi-spec input (treats -o as a directory when there are multiple inputs), and its validate script called spectral without npx. CI uses npx --yes directly anyway. Nothing else referenced it.
  4. Expanded no-internal-references rule — adds Convox, Sentry, Datadog, Grafana, Prometheus, Snowflake, OpenSearch, LaunchDarkly, AWS resource types (Kinesis, EventBridge, ECR/ECS/EKS/Fargate, Cognito, KMS, VPC, NACL), IaC tools (Pulumi, Terraform, CloudFormation), and our internal index/log names. All current specs still pass (0 errors).
  5. Rebuilt docs/index.html + Pages deployment workflow — old page pointed at a bundled/openapi-v3.yaml artifact that no longer exists. New page has a sidebar listing every public spec; clicking one loads it via ?spec=<name>. A new deploy-docs.yml workflow stages docs/ + specs/ into a flat _site/ and deploys via actions/deploy-pages. Pages source has already been switched to "GitHub Actions" via API.

The spec-list block in docs/index.html is wrapped in <!-- SPEC_LIST_START --> / <!-- SPEC_LIST_END --> markers so the upstream sync workflow can regenerate it on every release (workflow change is in lambda PR #4465; no-op until that PR lands).

Spec data sweep

I ran a regex sweep across every public v3 spec for AWS account numbers, internal hostnames, JWT-shaped tokens, and real-looking PII in example fields. Only "real-looking" hit was a 64-char hex string used as an example for the smileid-request-signature HMAC parameter — that's a synthetic example value, not a credential. No redactions needed.

Test plan

  • CI passes on this PR (validate workflow with the expanded ruleset)
  • After merge, deploy-docs.yml fires and deploys the new docs site to Pages
  • Visit the Pages URL and confirm the sidebar lists all 14 specs and they each render
  • Confirm LICENSE and SECURITY.md show up correctly on the GitHub repo landing page

PR Type

Enhancement, Documentation


Description

  • Add Apache-2.0 LICENSE for public repo compliance

  • Add SECURITY.md with vulnerability disclosure policy

  • Rebuild docs/index.html with multi-spec sidebar navigation

  • Expand spectral linting rule with more internal terms

  • Add GitHub Pages deployment workflow

  • Remove stale package.json


Diagram Walkthrough

flowchart LR
  A["Spectral Rule"] -- "expanded regex" --> B["Block internal terms"]
  C["docs/index.html"] -- "sidebar + spec list" --> D["Multi-spec Redoc viewer"]
  E["deploy-docs.yml"] -- "stages & deploys" --> F["GitHub Pages"]
  G["LICENSE + SECURITY.md"] -- "enables" --> H["Public repo readiness"]
Loading

File Walkthrough

Relevant files
Configuration changes
deploy-docs.yml
Add GitHub Pages deployment workflow                                         

.github/workflows/deploy-docs.yml

  • New workflow triggered on push to main for docs/** and specs/** paths
  • Stages docs/index.html and specs/v3/*.yaml into _site/ directory
  • Rewrites relative spec paths via sed for flat site layout
  • Deploys to GitHub Pages using actions/deploy-pages@v4
+51/-0   
Enhancement
.spectral.yaml
Expand spectral rule to block more internal terms               

.spectral.yaml

  • Greatly expanded no-internal-references regex with AWS services
    (Kinesis, EventBridge, ECR, ECS, EKS, Fargate, Cognito, KMS), IaC
    tools (Pulumi, Terraform, CloudFormation), databases (Snowflake,
    OpenSearch, Elasticsearch, MongoDB, DocumentDB, Aurora), and
    observability/internal tools (Convox, Sentry, Datadog, Grafana,
    Prometheus, LaunchDarkly, Sidekiq, etc.)
  • Added internal index names (enrollee_index, fraud_index)
  • Removed separate operation-operationId, operation-description, and
    info-description rule overrides
  • Updated rule description to be more comprehensive
+12/-12 
index.html
Rebuild docs page with multi-spec sidebar navigation         

docs/index.html

  • Replaced single-spec Redoc page with multi-spec viewer featuring a
    sidebar
  • Added SPECS array listing 14 API specs with markers for automation
  • Sidebar highlights active spec and supports navigation via ?spec=
    query parameter
  • Styled sidebar with Smile Identity brand color (#2B3990)
+87/-12 
Documentation
LICENSE
Add Apache-2.0 license file                                                           

LICENSE

  • Added full Apache License 2.0 text
  • Copyright attributed to Smile Identity Limited (2026)
+201/-0 
SECURITY.md
Add security vulnerability disclosure policy                         

SECURITY.md

  • Defines private vulnerability reporting via security@smileidentity.com
  • Specifies 3 business day acknowledgment and 10 business day response
    SLA
  • Outlines scope (spec inaccuracies, API vulnerabilities, CI
    supply-chain) and out-of-scope items
+31/-0   
Miscellaneous
package.json
Remove stale package.json                                                               

package.json

  • Removed entire file including validate and bundle scripts
  • Removed @stoplight/spectral-cli and @redocly/cli dev dependencies
+0/-14   


Need help?
  • Type /help how to ... in the comments thread for any questions about PR-Agent usage.
  • Check out the documentation for more information.
  • - Add Apache-2.0 LICENSE so consumers can legally generate clients
      from the specs.
    - Add SECURITY.md with private disclosure path
      (security@smileidentity.com).
    - Drop the stale package.json. The validate workflow uses
      `npx --yes` directly; nothing else referenced it. Its `bundle`
      script produced invalid output for multi-spec input anyway.
    - Expand the .spectral.yaml `no-internal-references` regex to block
      more internal infra/tooling names (Convox, Sentry, Datadog,
      Grafana, Prometheus, Snowflake, OpenSearch, LaunchDarkly, IaC
      tools, AWS-specific resource types, internal index names).
    - Rebuild docs/index.html for multi-spec rendering: sidebar lists
      every public spec, click reloads with ?spec=<name>. Replaces the
      page that was pointing at the now-deleted bundled artifact.
      Markers (`<!-- SPEC_LIST_START -->` / `<!-- SPEC_LIST_END -->`)
      let the upstream sync workflow regenerate the spec list.
    - Add deploy-docs.yml GitHub Actions Pages deployment. Stages
      docs/ + specs/ into a flat _site/, rewrites the spec paths, and
      publishes via actions/deploy-pages. Pages source has been
      switched from "branch /docs" to "GitHub Actions" out of band so
      this workflow can deploy.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    Copilot AI review requested due to automatic review settings May 8, 2026 11:47
    @prfectionist
    Copy link
    Copy Markdown

    prfectionist Bot commented May 8, 2026

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
    🏅 Score: 78
    🧪 No relevant tests
    🔒 No security concerns identified
    🔀 Multiple PR themes

    Sub-PR theme: Add Apache-2.0 LICENSE and SECURITY.md

    Relevant files:

    • LICENSE
    • SECURITY.md

    Sub-PR theme: Expand no-internal-references spectral rule

    Relevant files:

    • .spectral.yaml

    Sub-PR theme: Rebuild docs page with spec sidebar and deploy workflow

    Relevant files:

    • docs/index.html
    • .github/workflows/deploy-docs.yml

    ⚡ Recommended focus areas for review

    Regex Correctness

    The multi-line regex uses YAML block scalar (>-) which folds newlines into spaces. The resulting pattern will contain literal spaces between the alternation groups (e.g., between the closing ) and \b), and between each line's terms. Depending on how Spectral's pattern function compiles the regex, these embedded spaces may cause the pattern to never match or to match incorrectly. The regex should either be on a single line or use a format that strips all whitespace (e.g., the x flag is not available in Spectral's pattern function).

    notMatch: >-
      \b(
      DynamoDB|SQS|Lambda|S3|Kinesis|EventBridge|ECR|ECS|EKS|Fargate|Cognito|KMS|Pulumi|Terraform|CloudFormation|CDK|Parameter\ Store|SecretsManager|Secrets\ Manager|VPC|NACL
      |PostgreSQL|pgvector|pgbouncer|Redshift|Snowflake|OpenSearch|Elasticsearch|MongoDB|DocumentDB|Aurora
      |Convox|Sentry|Datadog|Grafana|Prometheus|LaunchDarkly|Optimizely|Sidekiq|Airflow|Mixpanel|Segment|FullStory
      |access_logs|embedding|enrollee_index|fraud_index
      )\b
    Missing HTML closing tag

    The <html lang="en"> opening tag is present but there is no closing </html> tag visible in the diff, nor closing </body> tag. This may cause rendering issues in some browsers or HTML validators.

    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>Smile Identity API Reference</title>
        <link
          href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700"
          rel="stylesheet"
        />
        <style>
          html, body { margin: 0; padding: 0; height: 100%; font-family: "Roboto", sans-serif; }
          #app { display: flex; height: 100vh; }
          #sidebar {
            width: 260px;
            flex: 0 0 260px;
            background: #2B3990;
            color: #fff;
            overflow-y: auto;
            padding: 24px 0;
          }
          #sidebar h1 {
            font-family: "Montserrat", sans-serif;
            font-size: 16px;
            font-weight: 700;
            margin: 0 0 16px;
            padding: 0 20px;
            letter-spacing: 0.04em;
            text-transform: uppercase;
            opacity: 0.8;
          }
          #sidebar ul { list-style: none; margin: 0; padding: 0; }
          #sidebar li a {
            display: block;
            padding: 8px 20px;
            color: #fff;
            text-decoration: none;
            font-size: 14px;
            border-left: 3px solid transparent;
            transition: background 0.15s, border-color 0.15s;
          }
          #sidebar li a:hover { background: rgba(255,255,255,0.07); }
          #sidebar li a.active {
            background: rgba(255,255,255,0.1);
            border-left-color: #fff;
            font-weight: 700;
          }
          #content { flex: 1; overflow: auto; }
        </style>
      </head>
      <body>
        <div id="app">
          <aside id="sidebar">
            <h1>Smile ID API</h1>
            <ul id="spec-list"></ul>
          </aside>
          <div id="content">
            <div id="redoc-container"></div>
          </div>
        </div>
    
        <script>
          // <!-- SPEC_LIST_START -->
          const SPECS = [
            { file: "v3-biometric-authentication-entry", label: "Biometric Authentication" },
            { file: "v3-biometric-enrollment-entry", label: "Biometric Enrollment" },
            { file: "v3-biometric-kyc-entry", label: "Biometric KYC" },
            { file: "v3-block-user", label: "Block User" },
            { file: "v3-document-verification-entry", label: "Document Verification" },
            { file: "v3-enhanced-document-verification-entry", label: "Enhanced Document Verification" },
            { file: "v3-enhanced-kyc-entry", label: "Enhanced KYC" },
            { file: "v3-enhanced-kyc-job-status", label: "Enhanced KYC Job Status" },
            { file: "v3-id-status", label: "ID Status" },
            { file: "v3-replay-callback", label: "Replay Callback" },
            { file: "v3-services", label: "Services" },
            { file: "v3-smart-selfie-compare-entry", label: "Smart Selfie Compare" },
            { file: "v3-supported-documents", label: "Supported Documents" },
            { file: "v3-token", label: "Token" }
          ];
          // <!-- SPEC_LIST_END -->
    
          const params = new URLSearchParams(window.location.search);
          const selected = SPECS.find(s => s.file === params.get("spec")) || SPECS[0];
    
          const list = document.getElementById("spec-list");
          for (const spec of SPECS) {
            const li = document.createElement("li");
            const a = document.createElement("a");
            a.href = `?spec=${encodeURIComponent(spec.file)}`;
            a.textContent = spec.label;
            if (spec.file === selected.file) a.className = "active";
            li.appendChild(a);
            list.appendChild(li);
          }
        </script>
        <script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
        <script>
          Redoc.init(
            `../specs/v3/${selected.file}.yaml`,
            {
              theme: {
                colors: { primary: { main: "#2B3990" } },
                typography: {
                  fontFamily: '"Roboto", sans-serif',
                  headings: { fontFamily: '"Montserrat", sans-serif' }
                }
              },
              hideDownloadButton: false,
              expandResponses: "200,201,202",
              nativeScrollbars: true
            },
            document.getElementById("redoc-container")
          );
        </script>
    Brittle glob pattern

    The step cp specs/v3/*.yaml _site/specs/v3/ will fail the workflow if no .yaml files exist in specs/v3/ (glob expands to literal *.yaml). While unlikely given the paths trigger, a defensive check or shopt -s nullglob would prevent unexpected failures.

    cp specs/v3/*.yaml _site/specs/v3/
    XSS via query param

    The selected.file value comes from URLSearchParams (user-controlled input) and is interpolated directly into the template literal passed to Redoc.init. While Redoc likely fetches it as a URL, a crafted ?spec= value could potentially be used for path traversal or, if Redoc renders error messages containing the URL, for reflected content injection. Consider validating that params.get("spec") matches one of the known SPECS entries before using it (the fallback to SPECS[0] only triggers if no match is found, but the find already handles this correctly — so this is actually safe as written).

      const selected = SPECS.find(s => s.file === params.get("spec")) || SPECS[0];
    
      const list = document.getElementById("spec-list");
      for (const spec of SPECS) {
        const li = document.createElement("li");
        const a = document.createElement("a");
        a.href = `?spec=${encodeURIComponent(spec.file)}`;
        a.textContent = spec.label;
        if (spec.file === selected.file) a.className = "active";
        li.appendChild(a);
        list.appendChild(li);
      }
    </script>
    <script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
    <script>
      Redoc.init(
        `../specs/v3/${selected.file}.yaml`,

    Comment thread .spectral.yaml Outdated
    Comment thread docs/index.html Outdated
    Copy link
    Copy Markdown

    Copilot AI left a comment

    Choose a reason for hiding this comment

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

    Pull request overview

    Pre-public-launch hardening for smileidentity/api-reference, aimed at clarifying repo licensing/security posture and restoring a working, multi-spec docs experience on GitHub Pages while tightening spec linting against internal-reference leakage.

    Changes:

    • Add Apache-2.0 LICENSE and a vulnerability disclosure policy in SECURITY.md.
    • Replace the single bundled-spec docs page with a sidebar-driven multi-spec selector in docs/index.html, and add a GitHub Pages deployment workflow.
    • Update .spectral.yaml to expand the no-internal-references denylist and remove the unused package.json.

    Reviewed changes

    Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

    Show a summary per file
    File Description
    SECURITY.md Adds a security policy and private disclosure contact information.
    package.json Removes unused Node scripts/devDependencies that are not referenced by CI.
    LICENSE Adds Apache-2.0 licensing for public distribution/consumption.
    docs/index.html Rebuilds docs UI to select among multiple public specs and load via query param.
    .spectral.yaml Expands internal-reference blocking rule and adjusts ruleset configuration.
    .github/workflows/deploy-docs.yml Adds Pages deployment workflow to publish docs/ + specs/ as a static site.

    💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

    Comment thread .spectral.yaml Outdated
    Comment thread .spectral.yaml
    Comment thread docs/index.html
    - Fix .spectral.yaml `no-internal-references` regex: switch from a
      YAML folded scalar (`>-`) to a single-line quoted string. The
      folded scalar inserts a space at every line break, which silently
      broke the alternation — most listed terms would never match. The
      rule appeared to "pass" only because no current spec contained
      the terms; a real leak would have slipped through.
    - Restore the three Spectral rules that were dropped in the
      rewrite: `operation-operationId: error`, `operation-description: warn`,
      `info-description: error`. These were intentional gates inherited
      from the original config and shouldn't have been removed.
    - Pin redoc.standalone.js to v2.4.0 instead of `latest`, so a
      breaking upstream release can't silently break the docs page.
      Skipping SRI for now — the standard CDN URL doesn't publish
      hashes per release and adding one adds upgrade friction.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    @beastawakens Ed Fricker (beastawakens) merged commit 107da1f into main May 8, 2026
    2 checks passed
    @beastawakens Ed Fricker (beastawakens) deleted the chore/pre-public-hardening branch May 8, 2026 12:16
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    2 participants