mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-02 02:07:25 +08:00
Redact runtime capabilities and require the SDK isolation check
HTTP request logs retained the runtime GitHub capability header. Add it to the shared redaction policy and cover success, denied, and failed requests with canary credentials. Run the real optional Sentry SDK regression in a dedicated CI job with the audited peer installed outside the workspace. Require the SDK in that job so missing installation cannot silently skip the regression. Validation: 123 focused tests, the canonical real-SDK CI command, server typecheck, module boundaries, YAML parse, and secret scans passed. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
name: Sentry SDK contract
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/sentry-contract.yml
|
||||
- server/package.json
|
||||
- server/src/sentry*.ts
|
||||
- server/src/peer-version-check.ts
|
||||
- server/src/__tests__/*sentry*.test.ts
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- .github/workflows/sentry-contract.yml
|
||||
- server/package.json
|
||||
- server/src/sentry*.ts
|
||||
- server/src/peer-version-check.ts
|
||||
- server/src/__tests__/*sentry*.test.ts
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: sentry-contract-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
sentry-contract:
|
||||
name: Real Sentry SDK isolation
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
with:
|
||||
version: 9.15.4
|
||||
|
||||
- name: Install workspace dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install the audited optional SDK outside the workspace
|
||||
shell: bash
|
||||
run: |
|
||||
sentry_version=$(node -p 'require("./server/package.json").peerDependencies["@sentry/node"]')
|
||||
npm install --prefix "$RUNNER_TEMP/sentry-contract-sdk" --ignore-scripts --no-audit --no-fund --package-lock=false "@sentry/node@$sentry_version"
|
||||
|
||||
- name: Verify run failure isolation with the real SDK
|
||||
env:
|
||||
NODE_PATH: ${{ runner.temp }}/sentry-contract-sdk/node_modules
|
||||
PAPERCLIP_REQUIRE_SENTRY_TEST_SDK: "1"
|
||||
run: pnpm --filter @paperclipai/server exec vitest run src/__tests__/run-failure-sentry-real-sdk.test.ts
|
||||
@@ -399,6 +399,35 @@ describe("HTTP logger redaction", () => {
|
||||
expect(log.res.headers["set-cookie"]).toBe("[Redacted]");
|
||||
});
|
||||
|
||||
it.each([200, 403, 500])("redacts runtime GitHub capabilities from HTTP %i logs", async (status) => {
|
||||
const capability = "runtime-github-capability-canary";
|
||||
const chunks: string[] = [];
|
||||
const stream = new Writable({
|
||||
write(chunk, _encoding, callback) {
|
||||
chunks.push(chunk.toString());
|
||||
callback();
|
||||
},
|
||||
});
|
||||
const app = express();
|
||||
app.use(createHttpLogger(pino({ redact: [...HTTP_LOG_REDACT_PATHS] }, stream)));
|
||||
app.post("/runtime-tools/github/credentials", (_req, res) => {
|
||||
res.status(status).json({ status });
|
||||
});
|
||||
|
||||
await request(app)
|
||||
.post("/runtime-tools/github/credentials")
|
||||
.set("X-Paperclip-Github-Capability", capability)
|
||||
.send({})
|
||||
.expect(status);
|
||||
|
||||
const output = chunks.join("");
|
||||
expect(output).not.toContain(capability);
|
||||
const log = JSON.parse(output.trim());
|
||||
expect(log.req.headers["x-paperclip-github-capability"]).toBe("[Redacted]");
|
||||
expect(log.req.url).toBe("/runtime-tools/github/credentials");
|
||||
expect(log.res.statusCode).toBe(status);
|
||||
});
|
||||
|
||||
it("drops OAuth callback query data from the message and structured request", async () => {
|
||||
const chunks: string[] = [];
|
||||
const stream = new Writable({
|
||||
|
||||
@@ -20,6 +20,10 @@ const sentryPackage = (() => {
|
||||
}
|
||||
})();
|
||||
|
||||
if (process.env.PAPERCLIP_REQUIRE_SENTRY_TEST_SDK === "1" && !sentryPackage) {
|
||||
throw new Error("The Sentry SDK contract job requires the audited optional peer");
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await sentryPackage?.close(2000);
|
||||
vi.unstubAllEnvs();
|
||||
|
||||
@@ -10,6 +10,8 @@ export const HTTP_LOG_REDACT_PATHS = [
|
||||
'req.headers["x-csrf-token"]',
|
||||
'req.headers["x-xsrf-token"]',
|
||||
'req.headers["x-api-key"]',
|
||||
// Runtime GitHub capabilities authorize credential acquisition for a live run.
|
||||
'req.headers["x-paperclip-github-capability"]',
|
||||
// Telegram's optional webhook verification header is a reusable bearer
|
||||
// secret sent on every provider callback.
|
||||
'req.headers["x-telegram-bot-api-secret-token"]',
|
||||
|
||||
Reference in New Issue
Block a user