Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions object-permission-inheritance-drift-guard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Object Permission Inheritance Drift Guard

Self-contained guard for SCIBASE issue #11. The module evaluates project role and object-level permission changes before they are applied, with a focus on inherited grants that can silently overexpose sensitive research objects.

## Scope

- Detects inherited project-role grants that overexpose restricted datasets, notebooks, manuscripts, or review threads.
- Flags stale object-level allow overrides that need recertification.
- Requires data-steward approval for pending sensitive object actions.
- Finds restricted inherited actions without explicit object-level override decisions.
- Produces remediation queues and deterministic JSON, Markdown, SVG, and MP4 review artifacts.

## Local Validation

```bash
npm run check
npm test
npm run demo
```

Generated artifacts are written to `reports/`.

## Integration Shape

`evaluateObjectPermissionInheritanceDrift()` accepts project packets with:

- `roles`
- `members`
- `objects`
- `objectOverrides`
- `stewardApprovals`
- `pendingPermissionChanges`

It returns project statuses:

- `permission_change_ready`
- `permission_review_required`
- `permission_change_blocked`

The output also includes a remediation queue, ready queue, effective grant summaries, and SHA-256 audit digest.
23 changes: 23 additions & 0 deletions object-permission-inheritance-drift-guard/acceptance-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Acceptance Notes

## Reviewer Checks

- A project with no sensitive inherited overexposure, no stale allow overrides, required steward approvals, and explicit restricted-action overrides is ready.
- A project with inherited restricted dataset download, review-thread read/comment, or notebook execute exposure is blocked.
- Stale object-level allow overrides produce review findings.
- Missing data-steward approval for sensitive pending actions blocks permission changes.
- Synthetic sample data contains no credentials, private user data, identity-provider calls, or production project data.

## Expected Commands

```bash
npm run check
npm test
npm run demo
```

Optional artifact validation:

```bash
ffprobe -v error -show_entries format=duration,size -show_entries stream=codec_name,width,height -of default=noprint_wrappers=1 reports/demo.mp4
```
89 changes: 89 additions & 0 deletions object-permission-inheritance-drift-guard/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
const fs = require("node:fs")
const path = require("node:path")
const { spawnSync } = require("node:child_process")
const { evaluateObjectPermissionInheritanceDrift } = require("./index")
const { projects, policy } = require("./sample-data")

const reportsDir = path.join(__dirname, "reports")
fs.mkdirSync(reportsDir, { recursive: true })

const packet = evaluateObjectPermissionInheritanceDrift({
asOf: "2026-05-23T21:50:00.000Z",
projects,
policy,
})

fs.writeFileSync(
path.join(reportsDir, "permission-drift-packet.json"),
`${JSON.stringify(packet, null, 2)}\n`,
)

const markdown = [
"# Object Permission Inheritance Drift Guard Report",
"",
`Projects reviewed: ${packet.summary.totalProjects}`,
`Ready projects: ${packet.summary.readyProjects}`,
`Blocked projects: ${packet.summary.blockedProjects}`,
`Objects reviewed: ${packet.summary.totalObjects}`,
`Sensitive objects: ${packet.summary.sensitiveObjects}`,
`Critical findings: ${packet.summary.criticalFindings}`,
`Warning findings: ${packet.summary.warningFindings}`,
`Audit digest: \`${packet.audit.digest}\``,
"",
"## Project Decisions",
...packet.projects.flatMap((project) => [
"",
`### ${project.title}`,
`- Status: ${project.status}`,
`- Objects: ${project.summary.objects}`,
`- Sensitive objects: ${project.summary.sensitiveObjects}`,
`- Finding codes: ${project.findings.map((finding) => finding.code).join(", ") || "none"}`,
]),
"",
"## Remediation Queue",
...packet.remediationQueue.map((item) => (
`- ${item.projectId}/${item.objectId}: ${item.action} (${item.severity})`
)),
"",
]

fs.writeFileSync(path.join(reportsDir, "permission-drift-report.md"), markdown.join("\n"))

const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540">
<rect width="960" height="540" fill="#0f172a"/>
<text x="48" y="78" fill="#f8fafc" font-family="Arial" font-size="32" font-weight="700">Object Permission Inheritance Drift Guard</text>
<text x="48" y="122" fill="#cbd5e1" font-family="Arial" font-size="18">Inherited grants, stale overrides, steward approvals, and object-level remediation</text>
<rect x="48" y="172" width="250" height="150" rx="8" fill="#047857"/>
<text x="78" y="232" fill="#ecfdf5" font-family="Arial" font-size="56" font-weight="700">${packet.summary.readyProjects}</text>
<text x="78" y="272" fill="#a7f3d0" font-family="Arial" font-size="22">ready</text>
<rect x="355" y="172" width="250" height="150" rx="8" fill="#be123c"/>
<text x="385" y="232" fill="#ffe4e6" font-family="Arial" font-size="56" font-weight="700">${packet.summary.blockedProjects}</text>
<text x="385" y="272" fill="#fecdd3" font-family="Arial" font-size="22">blocked</text>
<rect x="662" y="172" width="250" height="150" rx="8" fill="#2563eb"/>
<text x="692" y="232" fill="#dbeafe" font-family="Arial" font-size="56" font-weight="700">${packet.summary.sensitiveObjects}</text>
<text x="692" y="272" fill="#bfdbfe" font-family="Arial" font-size="22">sensitive objects</text>
<text x="48" y="386" fill="#e2e8f0" font-family="Arial" font-size="20">Controls protect datasets, notebooks, manuscripts, and review threads from inherited grant drift.</text>
<text x="48" y="426" fill="#94a3b8" font-family="Arial" font-size="16">Digest ${packet.audit.digest.slice(0, 28)}...</text>
</svg>
`

fs.writeFileSync(path.join(reportsDir, "summary.svg"), svg)

const ffmpeg = spawnSync("ffmpeg", [
"-y",
"-f",
"lavfi",
"-i",
"color=c=0x0f172a:s=960x540:d=5:r=15",
"-vf",
"drawbox=x=48:y=172:w=250:h=150:color=0x047857@1:t=fill,drawbox=x=355:y=172:w=250:h=150:color=0xbe123c@1:t=fill,drawbox=x=662:y=172:w=250:h=150:color=0x2563eb@1:t=fill,drawbox=x=48:y=368:w=864:h=18:color=0x38bdf8@1:t=fill",
"-pix_fmt",
"yuv420p",
path.join(reportsDir, "demo.mp4"),
], { stdio: "ignore" })

if (ffmpeg.status !== 0) {
console.warn("ffmpeg video generation failed; JSON, Markdown, and SVG reports were still generated.")
}

console.log(`Wrote object permission drift artifacts to ${reportsDir}`)
Loading