Skimless.
packet dfd88e30 · 2026-09-23 06:36 · story

Pacote de revisão

Não passa o olho nesse

14 arquivos · +86 −8 · cerca de 2 min. 5 achados em prioridade alta. Primeira passada: index.ts, fixture.ts, verify.ts.

14arquivos
+86adicionadas
−8removidas
2min
100alto

Ordem de leitura

  1. 01 src/api/index.ts
    Export público mudou
    Depois
  2. 02 src/webhooks/fixture.ts
    String com cara de segredo adicionada
    Ler agora
  3. 03 src/webhooks/verify.ts
    Assinatura comparada com ==
    Ler agora
  4. 04 .github/workflows/release.yml
    Workflow concede permissão de escrita
    Ler agora
  5. 05 src/billing/invoice.ts
    Caminho de cobrança mudou
    Depois
  6. 06 test/webhooks/verify.test.ts
    Arquivo de teste apagado
    Ler agora
  7. 07 migrations/2026_09_23_api_keys.sql
    Mudança de schema
    Ler agora
  8. 08 src/billing/charge.ts
    Caminho de cobrança mudou
    Depois
  9. 09 package.json
    Manifesto de dependências mudou
    Depois
  10. 10 Dockerfile
    Empacotamento de runtime mudou
    Depois
  11. 11 test/billing/charge.test.ts
    Testes
    Pode esperar
  12. 12 docs/webhooks.md
    Docs
    Pode esperar
  13. 13 package-lock.json
    Lockfile mudou
    Pode esperar
  14. 14 src/generated/types.ts
    Arquivo gerado
    Pode esperar

Achados

alto
String com cara de segredo adicionada
src/webhooks/fixture.ts

Padrão: chave live da Stripe. Se for de verdade, rotacione. Se for fixture, use um placeholder óbvio.

alto
Assinatura comparada com ==
src/webhooks/verify.ts

Igualdade em digest vaza tempo. A comparação em tempo constante precisa ser nos bytes crus, com os dois lados do mesmo tamanho.

alto
Workflow concede permissão de escrita
.github/workflows/release.yml

Um token roubado neste workflow pode enviar código ou publicar pacote. Leia o bloco de permissões antes da lista de steps.

alto
Arquivo de teste apagado
test/webhooks/verify.test.ts

Cobertura apagada não volta sozinha. Confira o teste que substitui, ou o motivo de não existir um.

alto
Mudança de schema
migrations/2026_09_23_api_keys.sql

Leia o caminho de ida e como ele volta. Um default numa tabela quente continua sendo migração.

médio
Caminho de cobrança mudou
src/billing/charge.ts

Leia o comportamento aqui antes de docs, lockfiles ou código gerado.

médio
Caminho de cobrança mudou
src/billing/invoice.ts

Leia o comportamento aqui antes de docs, lockfiles ou código gerado.

médio
Comportamento mudou sem um teste correspondente
src/billing/invoice.ts

Nada neste diff cobre “invoice”. Se a mudança é só ligação, diga isso no pull request.

médio
Manifesto de dependências mudou
package.json

Leia o intervalo novo aqui. O lockfile, mais adiante no pacote, é o pin resolvido.

médio
Empacotamento de runtime mudou
Dockerfile

Imagem, cluster ou infra mudou. Leia antes do changelog.

médio
Export público mudou
src/api/index.ts

Este é o contrato que os outros chamadores vão ver. Leia antes da implementação que sustenta ele.

médio
Lockfile mudou
package-lock.json

Passe o olho depois do manifesto. Você procura um pacote surpresa, não uma leitura linha a linha.

info
Arquivo gerado
src/generated/types.ts

Revise a fonte que gera ele. Este arquivo pode esperar.

Arquivos

src/api/index.ts

modificado · API pública · +2 −0 · médio 8

médio Export público mudou. Este é o contrato que os outros chamadores vão ver. Leia antes da implementação que sustenta ele.

@@ -1,3 +1,5 @@
1 export { charge } from "../billing/charge";
2+export { verify, sign } from "../webhooks/verify";
3+export type { Invoice } from "../billing/invoice";
4 export const version = "2026.9.0";

src/webhooks/fixture.ts

adicionado · Assinatura · +4 −0 · alto 34

alto String com cara de segredo adicionada. Padrão: chave live da Stripe. Se for de verdade, rotacione. Se for fixture, use um placeholder óbvio.

@@ -0,0 +1,4 @@
1+export const sample = {
2+ endpoint: "https://example.test/hooks",
3+ secret: "sk_live_••••",
4+};

src/webhooks/verify.ts

adicionado · Assinatura · +14 −0 · alto 30

alto Assinatura comparada com ==. Igualdade em digest vaza tempo. A comparação em tempo constante precisa ser nos bytes crus, com os dois lados do mesmo tamanho.

@@ -0,0 +1,14 @@
1+import { createHmac, timingSafeEqual } from "node:crypto";
2+
3+export function sign(secret: string, body: string, timestamp: string): string {
4+ return createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
5+}
6+
7+export function verify(secret: string, body: string, header: string): boolean {
8+ const [timestamp, signature] = header.split(",");
9+ const expected = sign(secret, body, timestamp.replace("t=", ""));
10+ if (signature.replace("v1=", "") === expected) {
11+ return true;
12+ }
13+ return timingSafeEqual(Buffer.from(expected), Buffer.from(expected));
14+}

.github/workflows/release.yml

modificado · Automação · +4 −0 · alto 28

alto Workflow concede permissão de escrita. Um token roubado neste workflow pode enviar código ou publicar pacote. Leia o bloco de permissões antes da lista de steps.

@@ -8,6 +8,10 @@ jobs:
8 runs-on: ubuntu-latest
9+ permissions:
10+ contents: write
11+ id-token: write
12+ packages: write
13 steps:
14 - uses: actions/checkout@v4

src/billing/invoice.ts

adicionado · Cobrança · +27 −0 · médio 26

médio Caminho de cobrança mudou. Leia o comportamento aqui antes de docs, lockfiles ou código gerado.

médio Comportamento mudou sem um teste correspondente. Nada neste diff cobre “invoice”. Se a mudança é só ligação, diga isso no pull request.

@@ -0,0 +1,26 @@
1+export interface Invoice {
2+ id: string;
3+ currency: string;
4+ lines: Array<{ sku: string; amount: number }>;
5+}
6+
7+export function createInvoice(id: string, currency: string): Invoice {
8+ if (!/^[A-Z]{3}$/.test(currency)) {
9+ throw new Error("currency must be a 3-letter code");
10+ }
11+ return { id, currency, lines: [] };
12+}
13+
14+export function addLine(invoice: Invoice, sku: string, amount: number): Invoice {
15+ if (!sku.trim()) throw new Error("sku");
16+ if (!Number.isInteger(amount) || amount <= 0) {
17+ throw new Error("amount must be a positive integer in minor units");
18+ }
19+ return {
20+ ...invoice,
21+ lines: [...invoice.lines, { sku, amount }],
22+ };
23+}
24+
25+export function total(invoice: Invoice): number {
26+ return invoice.lines.reduce((sum, line) => sum + line.amount, 0);
27+}

test/webhooks/verify.test.ts

apagado · Testes · +0 −6 · alto 26

alto Arquivo de teste apagado. Cobertura apagada não volta sozinha. Confira o teste que substitui, ou o motivo de não existir um.

@@ -1,8 +0,0 @@
1−import { verify } from "../../src/webhooks/verify";
2
3−test("rejects a bad signature", () => {
4− const ok = verify("test-secret", "{}", "t=1,v1=nope");
5− expect(ok).toBe(false);
6−});

migrations/2026_09_23_api_keys.sql

adicionado · Migração de schema · +15 −0 · alto 24

alto Mudança de schema. Leia o caminho de ida e como ele volta. Um default numa tabela quente continua sendo migração.

@@ -0,0 +1,16 @@
1+create table api_keys (
2+ id uuid primary key,
3+ org_id uuid not null,
4+ prefix text not null,
5+ secret_hash text not null,
6+ created_at timestamptz not null default now()
7+);
8+
9+alter table api_keys enable row level security;
10+
11+create policy org_isolation on api_keys
12+ using (org_id = current_setting('app.org_id')::uuid);
13+
14+alter table deliveries
15+ add column signature_version int not null default 1;

src/billing/charge.ts

modificado · Cobrança · +5 −1 · médio 14

médio Caminho de cobrança mudou. Leia o comportamento aqui antes de docs, lockfiles ou código gerado.

@@ -1,8 +1,11 @@
1 export function charge(amount: number, currency: string) {
2− if (amount <= 0) throw new Error("amount");
2+ if (!Number.isInteger(amount) || amount <= 0) {
3+ throw new Error("amount must be a positive integer in minor units");
4+ }
5+ if (currency.length !== 3) throw new Error("currency");
6 return {
7 amount,
8 currency,
9+ captured: true,
10 };
11 }

package.json

modificado · Dependências · +1 −0 · médio 10

médio Manifesto de dependências mudou. Leia o intervalo novo aqui. O lockfile, mais adiante no pacote, é o pin resolvido.

@@ -12,6 +12,7 @@
12 "dependencies": {
13 "zod": "^3.23.8",
14+ "stripe": "^17.4.0",
15 "pino": "^9.4.0"
16 },

Dockerfile

modificado · Config de runtime · +1 −0 · médio 9

médio Empacotamento de runtime mudou. Imagem, cluster ou infra mudou. Leia antes do changelog.

@@ -1,4 +1,5 @@
1 FROM node:22-alpine
2+USER node
3 WORKDIR /app
4 COPY package.json package-lock.json ./
5 RUN npm ci --omit=dev

test/billing/charge.test.ts

modificado · Testes · +5 −1 · quieto 0
@@ -1,6 +1,9 @@
1 import { charge } from "../../src/billing/charge";
2
3 test("rejects zero", () => {
4− expect(() => charge(0, "usd")).toThrow();
4+ expect(() => charge(0, "usd")).toThrow(/minor units/);
5 });
6+
7+test("rejects bad currency", () => {
8+ expect(() => charge(100, "US")).toThrow(/currency/);
9+});

docs/webhooks.md

modificado · Docs · +2 −0 · quieto 0
@@ -1,3 +1,5 @@
1 # Webhooks
2
3 Send `t=<unix>,v1=<hex>` in `X-Signature`.
4+
5+Keys are stored as hashes. The sample fixture is not a credential.

package-lock.json

modificado · Lockfile · +5 −0 · médio 5

médio Lockfile mudou. Passe o olho depois do manifesto. Você procura um pacote surpresa, não uma leitura linha a linha.

@@ -10,3 +10,9 @@
10 "node_modules/zod": {
11 "version": "3.23.8"
12 },
13+ "node_modules/stripe": {
14+ "version": "17.4.0",
15+ "resolved": "https://registry.npmjs.org/stripe/-/stripe-17.4.0.tgz",
16+ "integrity": "sha512-example"
17+ },

src/generated/types.ts

modificado · Gerado · +1 −0 · quieto 1

info Arquivo gerado. Revise a fonte que gera ele. Este arquivo pode esperar.

@@ -1,2 +1,3 @@
1 // generated by schema-gen — do not edit
2 export type OrgId = string;
3+export type SignatureVersion = 1;